Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HEAD request for http check #2474

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type AgentServiceCheck struct {
Status string `json:",omitempty"`
Notes string `json:",omitempty"`
TLSSkipVerify bool `json:",omitempty"`
Head bool `json:",omitempty"`

// In Consul 0.7 and later, checks that are associated with a service
// may also contain this optional DeregisterCriticalServiceAfter field,
Expand Down
1 change: 1 addition & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *CheckType, persist
Timeout: chkType.Timeout,
Logger: a.logger,
TLSSkipVerify: chkType.TLSSkipVerify,
Head: chkType.Head,
}
http.Start()
a.checkHTTPs[check.CheckID] = http
Expand Down
8 changes: 7 additions & 1 deletion command/agent/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type CheckType struct {
DockerContainerID string
Shell string
TLSSkipVerify bool
Head bool

Timeout time.Duration
TTL time.Duration
Expand Down Expand Up @@ -342,6 +343,7 @@ type CheckHTTP struct {
Timeout time.Duration
Logger *log.Logger
TLSSkipVerify bool
Head bool

httpClient *http.Client
stop bool
Expand Down Expand Up @@ -420,7 +422,11 @@ func (c *CheckHTTP) run() {

// check is invoked periodically to perform the HTTP check
func (c *CheckHTTP) check() {
req, err := http.NewRequest("GET", c.HTTP, nil)
method := http.MethodGet
if c.Head {
method = http.MethodHead
}
req, err := http.NewRequest(method, c.HTTP, nil)
if err != nil {
c.Logger.Printf("[WARN] agent: http request failed '%s': %s", c.HTTP, err)
c.Notify.UpdateCheck(c.CheckID, structs.HealthCritical, err.Error())
Expand Down
44 changes: 41 additions & 3 deletions command/agent/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http/httptest"
"os"
"os/exec"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -213,10 +214,12 @@ func TestCheckTTL(t *testing.T) {
func mockHTTPServer(responseCode int) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Body larger than 4k limit
body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize)
w.WriteHeader(responseCode)
w.Write(body)
if r.Method != http.MethodHead {
// Body larger than 4k limit
body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize)
w.Write(body)
}
return
})

Expand Down Expand Up @@ -535,6 +538,41 @@ func mockTCPServer(network string) net.Listener {

return listener
}
func TestCheckHTTP_Head_true_empty_output(t *testing.T) {
server := mockHTTPServer(200)
defer server.Close()

mock := &MockNotify{
state: make(map[types.CheckID]string),
updates: make(map[types.CheckID]int),
output: make(map[types.CheckID]string),
}

check := &CheckHTTP{
Notify: mock,
CheckID: types.CheckID("head_true"),
HTTP: server.URL,
Interval: 5 * time.Millisecond,
Logger: log.New(os.Stderr, "", log.LstdFlags),
Head: true,
}

check.Start()
defer check.Stop()

testutil.WaitForResult(func() (bool, error) {
if mock.state["head_true"] != structs.HealthPassing {
return false, fmt.Errorf("should be passing '%v'", mock.state)
}
match, _ := regexp.MatchString("^.* Output: $", mock.output["head_true"])
if !match {
return false, fmt.Errorf("check output should be empty '%v'", mock.output["head_true"])
}
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
})
}

func expectTCPStatus(t *testing.T, tcp string, status string) {
mock := &MockNotify{
Expand Down
16 changes: 15 additions & 1 deletion command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -1099,7 +1100,7 @@ AFTER_FIX:
}

func FixupCheckType(raw interface{}) error {
var ttlKey, intervalKey, timeoutKey string
var ttlKey, intervalKey, timeoutKey, headKey string
const deregisterKey = "DeregisterCriticalServiceAfter"

// Handle decoding of time durations
Expand All @@ -1116,6 +1117,8 @@ func FixupCheckType(raw interface{}) error {
intervalKey = k
case "timeout":
timeoutKey = k
case "head":
headKey = k
case "deregister_critical_service_after":
rawMap[deregisterKey] = v
delete(rawMap, k)
Expand All @@ -1131,6 +1134,17 @@ func FixupCheckType(raw interface{}) error {
}
}

if head, ok := rawMap[headKey]; ok {
headS, ok := head.(string)
if ok {
if bol, err := strconv.ParseBool(headS); err != nil {
return err
} else {
rawMap[headKey] = bol
}
}
}

if ttl, ok := rawMap[ttlKey]; ok {
ttlS, ok := ttl.(string)
if ok {
Expand Down
20 changes: 20 additions & 0 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,15 @@ func TestDecodeConfig_Checks(t *testing.T) {
"timeout": "100ms",
"service_id": "insecure-sslservice",
"tls_skip_verify": true
},
{
"id": "chk7",
"name": "service:elasticsearch:health",
"HTTP": "http://localhost:9200/_cluster_health",
"interval": "10s",
"timeout": "100ms",
"service_id": "elasticsearch",
"head": true
}
]
}`
Expand Down Expand Up @@ -1316,6 +1325,17 @@ func TestDecodeConfig_Checks(t *testing.T) {
TLSSkipVerify: true,
},
},
&CheckDefinition{
ID: "chk7",
Name: "service:elasticsearch:health",
ServiceID: "elasticsearch",
CheckType: CheckType{
HTTP: "http://localhost:9200/_cluster_health",
Interval: 10 * time.Second,
Timeout: 100 * time.Millisecond,
Head: true,
},
},
},
}

Expand Down
3 changes: 2 additions & 1 deletion website/source/docs/agent/checks.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ There are five different kinds of checks:
limited to roughly 4K. Responses larger than this will be truncated. HTTP checks
also support SSL. By default, a valid SSL certificate is expected. Certificate
verification can be turned off by setting the `tls_skip_verify` field to `true`
in the check definition.
in the check definition. The request will be , by default, a `GET` but can be
turned into a `HEAD` by setting `head` field to `true`.

* TCP + Interval - These checks make an TCP connection attempt every Interval
(e.g. every 30 seconds) to the specified IP/hostname and port. If no hostname
Expand Down
3 changes: 2 additions & 1 deletion website/source/docs/agent/http/agent.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ body must look like:
"TCP": "example.com:22",
"Interval": "10s",
"TTL": "15s",
"TLSSkipVerify": true
"TLSSkipVerify": true,
"Head": true,
}
```

Expand Down