feat: support sending body in HTTP checks (#6602)

This commit is contained in:
Akshay Ganeshen 2020-02-10 11:27:12 -05:00 committed by GitHub
parent 4f21bbdb4e
commit 8beb716414
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 120 additions and 3 deletions

View File

@ -2911,6 +2911,7 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType,
HTTP: chkType.HTTP,
Header: chkType.Header,
Method: chkType.Method,
Body: chkType.Body,
Interval: chkType.Interval,
Timeout: chkType.Timeout,
Logger: a.logger,

View File

@ -8,6 +8,7 @@ import (
"net/http"
"os"
osexec "os/exec"
"strings"
"sync"
"syscall"
"time"
@ -333,6 +334,7 @@ type CheckHTTP struct {
HTTP string
Header map[string][]string
Method string
Body string
Interval time.Duration
Timeout time.Duration
Logger hclog.Logger
@ -355,6 +357,7 @@ func (c *CheckHTTP) CheckType() structs.CheckType {
CheckID: c.CheckID.ID,
HTTP: c.HTTP,
Method: c.Method,
Body: c.Body,
Header: c.Header,
Interval: c.Interval,
ProxyHTTP: c.ProxyHTTP,
@ -435,7 +438,8 @@ func (c *CheckHTTP) check() {
target = c.ProxyHTTP
}
req, err := http.NewRequest(method, target, nil)
bodyReader := strings.NewReader(c.Body)
req, err := http.NewRequest(method, target, bodyReader)
if err != nil {
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
return

View File

@ -586,6 +586,74 @@ func TestCheckHTTPTimeout(t *testing.T) {
})
}
func TestCheckHTTPBody(t *testing.T) {
t.Parallel()
timeout := 5 * time.Millisecond
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
buf bytes.Buffer
body []byte
)
code := 200
if _, err := buf.ReadFrom(r.Body); err != nil {
code = 999
body = []byte(err.Error())
} else {
body = buf.Bytes()
}
w.WriteHeader(code)
w.Write(body)
}))
defer server.Close()
tests := []struct {
desc string
method string
header http.Header
body string
}{
{desc: "get body", method: "GET", body: "hello world"},
{desc: "post body", method: "POST", body: "hello world"},
{desc: "post json body", header: http.Header{"Content-Type": []string{"application/json"}}, method: "POST", body: "{\"foo\":\"bar\"}"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
notif := mock.NewNotify()
cid := structs.NewCheckID("checkbody", nil)
logger := testutil.Logger(t)
check := &CheckHTTP{
CheckID: cid,
HTTP: server.URL,
Header: tt.header,
Method: tt.method,
Body: tt.body,
Timeout: timeout,
Interval: 2 * time.Millisecond,
Logger: logger,
StatusHandler: NewStatusHandler(notif, logger, 0, 0),
}
check.Start()
defer check.Stop()
retry.Run(t, func(r *retry.R) {
if got, want := notif.Updates(cid), 2; got < want {
r.Fatalf("got %d updates want at least %d", got, want)
}
if got, want := notif.State(cid), api.HealthPassing; got != want {
r.Fatalf("got status %q want %q", got, want)
}
if got, want := notif.Output(cid), tt.body; !strings.HasSuffix(got, want) {
r.Fatalf("got output %q want suffix %q", got, want)
}
})
})
}
}
func TestCheckHTTP_disablesKeepAlives(t *testing.T) {
t.Parallel()
notif := mock.NewNotify()

View File

@ -1287,6 +1287,7 @@ func (b *Builder) checkVal(v *CheckDefinition) *structs.CheckDefinition {
HTTP: b.stringVal(v.HTTP),
Header: v.Header,
Method: b.stringVal(v.Method),
Body: b.stringVal(v.Body),
TCP: b.stringVal(v.TCP),
Interval: b.durationVal(fmt.Sprintf("check[%s].interval", id), v.Interval),
DockerContainerID: b.stringVal(v.DockerContainerID),

View File

@ -418,6 +418,7 @@ type CheckDefinition struct {
HTTP *string `json:"http,omitempty" hcl:"http" mapstructure:"http"`
Header map[string][]string `json:"header,omitempty" hcl:"header" mapstructure:"header"`
Method *string `json:"method,omitempty" hcl:"method" mapstructure:"method"`
Body *string `json:"body,omitempty" hcl:"body" mapstructure:"body"`
OutputMaxSize *int `json:"output_max_size,omitempty" hcl:"output_max_size" mapstructure:"output_max_size"`
TCP *string `json:"tcp,omitempty" hcl:"tcp" mapstructure:"tcp"`
Interval *string `json:"interval,omitempty" hcl:"interval" mapstructure:"interval"`

View File

@ -3738,6 +3738,7 @@ func TestFullConfig(t *testing.T) {
"f3r6xFtM": [ "RyuIdDWv", "QbxEcIUM" ]
},
"method": "Dou0nGT5",
"body": "5PBQd2OT",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
"tcp": "JY6fTTcw",
"interval": "18714s",
@ -3763,6 +3764,7 @@ func TestFullConfig(t *testing.T) {
"Ui0nU99X": [ "LMccm3Qe", "k5H5RggQ" ]
},
"method": "aldrIQ4l",
"body": "wSjTy7dg",
"tcp": "RJQND605",
"interval": "22164s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -3787,6 +3789,7 @@ func TestFullConfig(t *testing.T) {
"qxvdnSE9": [ "6wBPUYdF", "YYh8wtSZ" ]
},
"method": "gLrztrNw",
"body": "0jkKgGUC",
"tcp": "4jG5casb",
"interval": "28767s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4005,6 +4008,7 @@ func TestFullConfig(t *testing.T) {
"l4HwQ112": ["fk56MNlo", "dhLK56aZ"]
},
"method": "9afLm3Mj",
"body": "wVVL2V6f",
"tcp": "fjiLFqVd",
"interval": "23926s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4028,6 +4032,7 @@ func TestFullConfig(t *testing.T) {
"SHOVq1Vv": [ "jntFhyym", "GYJh32pp" ]
},
"method": "T66MFBfR",
"body": "OwGjTFQi",
"tcp": "bNnNfx2A",
"interval": "22224s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4050,6 +4055,7 @@ func TestFullConfig(t *testing.T) {
"p2UI34Qz": [ "UsG1D0Qh", "NHhRiB6s" ]
},
"method": "ciYHWors",
"body": "lUVLGYU7",
"tcp": "FfvCwlqH",
"interval": "12356s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4086,6 +4092,7 @@ func TestFullConfig(t *testing.T) {
"cVFpko4u": ["gGqdEB6k", "9LsRo22u"]
},
"method": "X5DrovFc",
"body": "WeikigLh",
"tcp": "ICbxkpSF",
"interval": "24392s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4125,6 +4132,7 @@ func TestFullConfig(t *testing.T) {
"1UJXjVrT": [ "OJgxzTfk", "xZZrFsq7" ]
},
"method": "5wkAxCUE",
"body": "7CRjCJyz",
"tcp": "MN3oA9D2",
"interval": "32718s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4147,6 +4155,7 @@ func TestFullConfig(t *testing.T) {
"vr7wY7CS": [ "EtCoNPPL", "9vAarJ5s" ]
},
"method": "wzByP903",
"body": "4I8ucZgZ",
"tcp": "2exjZIGE",
"interval": "5656s",
"output_max_size": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -4356,6 +4365,7 @@ func TestFullConfig(t *testing.T) {
f3r6xFtM = [ "RyuIdDWv", "QbxEcIUM" ]
}
method = "Dou0nGT5"
body = "5PBQd2OT"
tcp = "JY6fTTcw"
interval = "18714s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4381,6 +4391,7 @@ func TestFullConfig(t *testing.T) {
"Ui0nU99X" = [ "LMccm3Qe", "k5H5RggQ" ]
}
method = "aldrIQ4l"
body = "wSjTy7dg"
tcp = "RJQND605"
interval = "22164s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4405,6 +4416,7 @@ func TestFullConfig(t *testing.T) {
"qxvdnSE9" = [ "6wBPUYdF", "YYh8wtSZ" ]
}
method = "gLrztrNw"
body = "0jkKgGUC"
tcp = "4jG5casb"
interval = "28767s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4627,6 +4639,7 @@ func TestFullConfig(t *testing.T) {
l4HwQ112 = [ "fk56MNlo", "dhLK56aZ" ]
}
method = "9afLm3Mj"
body = "wVVL2V6f"
tcp = "fjiLFqVd"
interval = "23926s"
docker_container_id = "dO5TtRHk"
@ -4649,6 +4662,7 @@ func TestFullConfig(t *testing.T) {
"SHOVq1Vv" = [ "jntFhyym", "GYJh32pp" ]
}
method = "T66MFBfR"
body = "OwGjTFQi"
tcp = "bNnNfx2A"
interval = "22224s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4671,6 +4685,7 @@ func TestFullConfig(t *testing.T) {
"p2UI34Qz" = [ "UsG1D0Qh", "NHhRiB6s" ]
}
method = "ciYHWors"
body = "lUVLGYU7"
tcp = "FfvCwlqH"
interval = "12356s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4707,6 +4722,7 @@ func TestFullConfig(t *testing.T) {
cVFpko4u = [ "gGqdEB6k", "9LsRo22u" ]
}
method = "X5DrovFc"
body = "WeikigLh"
tcp = "ICbxkpSF"
interval = "24392s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4746,6 +4762,7 @@ func TestFullConfig(t *testing.T) {
"1UJXjVrT" = [ "OJgxzTfk", "xZZrFsq7" ]
}
method = "5wkAxCUE"
body = "7CRjCJyz"
tcp = "MN3oA9D2"
interval = "32718s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -4768,6 +4785,7 @@ func TestFullConfig(t *testing.T) {
"vr7wY7CS" = [ "EtCoNPPL", "9vAarJ5s" ]
}
method = "wzByP903"
body = "4I8ucZgZ"
tcp = "2exjZIGE"
interval = "5656s"
output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
@ -5071,6 +5089,7 @@ func TestFullConfig(t *testing.T) {
"Ui0nU99X": []string{"LMccm3Qe", "k5H5RggQ"},
},
Method: "aldrIQ4l",
Body: "wSjTy7dg",
TCP: "RJQND605",
Interval: 22164 * time.Second,
OutputMaxSize: checks.DefaultBufSize,
@ -5095,6 +5114,7 @@ func TestFullConfig(t *testing.T) {
"qxvdnSE9": []string{"6wBPUYdF", "YYh8wtSZ"},
},
Method: "gLrztrNw",
Body: "0jkKgGUC",
OutputMaxSize: checks.DefaultBufSize,
TCP: "4jG5casb",
Interval: 28767 * time.Second,
@ -5119,6 +5139,7 @@ func TestFullConfig(t *testing.T) {
"f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"},
},
Method: "Dou0nGT5",
Body: "5PBQd2OT",
OutputMaxSize: checks.DefaultBufSize,
TCP: "JY6fTTcw",
Interval: 18714 * time.Second,
@ -5293,6 +5314,7 @@ func TestFullConfig(t *testing.T) {
"cVFpko4u": {"gGqdEB6k", "9LsRo22u"},
},
Method: "X5DrovFc",
Body: "WeikigLh",
OutputMaxSize: checks.DefaultBufSize,
TCP: "ICbxkpSF",
Interval: 24392 * time.Second,
@ -5342,6 +5364,7 @@ func TestFullConfig(t *testing.T) {
"1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"},
},
Method: "5wkAxCUE",
Body: "7CRjCJyz",
OutputMaxSize: checks.DefaultBufSize,
TCP: "MN3oA9D2",
Interval: 32718 * time.Second,
@ -5364,6 +5387,7 @@ func TestFullConfig(t *testing.T) {
"vr7wY7CS": {"EtCoNPPL", "9vAarJ5s"},
},
Method: "wzByP903",
Body: "4I8ucZgZ",
OutputMaxSize: checks.DefaultBufSize,
TCP: "2exjZIGE",
Interval: 5656 * time.Second,
@ -5478,6 +5502,7 @@ func TestFullConfig(t *testing.T) {
"SHOVq1Vv": {"jntFhyym", "GYJh32pp"},
},
Method: "T66MFBfR",
Body: "OwGjTFQi",
OutputMaxSize: checks.DefaultBufSize,
TCP: "bNnNfx2A",
Interval: 22224 * time.Second,
@ -5500,6 +5525,7 @@ func TestFullConfig(t *testing.T) {
"p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"},
},
Method: "ciYHWors",
Body: "lUVLGYU7",
OutputMaxSize: checks.DefaultBufSize,
TCP: "FfvCwlqH",
Interval: 12356 * time.Second,
@ -5522,6 +5548,7 @@ func TestFullConfig(t *testing.T) {
"l4HwQ112": {"fk56MNlo", "dhLK56aZ"},
},
Method: "9afLm3Mj",
Body: "wVVL2V6f",
OutputMaxSize: checks.DefaultBufSize,
TCP: "fjiLFqVd",
Interval: 23926 * time.Second,
@ -5972,6 +5999,7 @@ func TestSanitize(t *testing.T) {
"ID": "",
"Interval": "0s",
"Method": "",
"Body": "",
"Name": "zoo",
"Notes": "",
"OutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `,
@ -6155,6 +6183,7 @@ func TestSanitize(t *testing.T) {
"Header": {},
"Interval": "0s",
"Method": "",
"Body": "",
"Name": "blurb",
"Notes": "",
"OutputMaxSize": ` + strconv.Itoa(checks.DefaultBufSize) + `,

View File

@ -2671,6 +2671,7 @@ func TestDecodeSessionCreate(t *testing.T) {
// HTTP string
// Header map[string][]string
// Method string
// Body string
// TLSSkipVerify bool
// TCP string
// IntervalDuration time.Duration

View File

@ -26,6 +26,7 @@ type CheckDefinition struct {
HTTP string
Header map[string][]string
Method string
Body string
TCP string
Interval time.Duration
DockerContainerID string
@ -171,6 +172,7 @@ func (c *CheckDefinition) CheckType() *CheckType {
GRPCUseTLS: c.GRPCUseTLS,
Header: c.Header,
Method: c.Method,
Body: c.Body,
OutputMaxSize: c.OutputMaxSize,
TCP: c.TCP,
Interval: c.Interval,

View File

@ -34,6 +34,7 @@ type CheckType struct {
HTTP string
Header map[string][]string
Method string
Body string
TCP string
Interval time.Duration
AliasNode string

View File

@ -1318,6 +1318,7 @@ type HealthCheckDefinition struct {
TLSSkipVerify bool `json:",omitempty"`
Header map[string][]string `json:",omitempty"`
Method string `json:",omitempty"`
Body string `json:",omitempty"`
TCP string `json:",omitempty"`
Interval time.Duration `json:",omitempty"`
OutputMaxSize uint `json:",omitempty"`
@ -1463,6 +1464,7 @@ func (c *HealthCheck) CheckType() *CheckType {
GRPCUseTLS: c.Definition.GRPCUseTLS,
Header: c.Definition.Header,
Method: c.Definition.Method,
Body: c.Definition.Body,
TCP: c.Definition.TCP,
Interval: c.Definition.Interval,
DockerContainerID: c.Definition.DockerContainerID,

View File

@ -240,6 +240,7 @@ func (s *HTTPServer) convertOps(resp http.ResponseWriter, req *http.Request) (st
TLSSkipVerify: check.Definition.TLSSkipVerify,
Header: check.Definition.Header,
Method: check.Definition.Method,
Body: check.Definition.Body,
TCP: check.Definition.TCP,
Interval: interval,
Timeout: timeout,

View File

@ -190,6 +190,7 @@ type AgentServiceCheck struct {
HTTP string `json:",omitempty"`
Header map[string][]string `json:",omitempty"`
Method string `json:",omitempty"`
Body string `json:",omitempty"`
TCP string `json:",omitempty"`
Status string `json:",omitempty"`
Notes string `json:",omitempty"`

View File

@ -51,6 +51,7 @@ type HealthCheckDefinition struct {
HTTP string
Header map[string][]string
Method string
Body string
TLSSkipVerify bool
TCP string
IntervalDuration time.Duration `json:"-"`

View File

@ -176,6 +176,8 @@ The table below shows this endpoint's support for
- `Method` `(string: "")` - Specifies a different HTTP method to be used
for an `HTTP` check. When no value is specified, `GET` is used.
- `Body` `(string: "")` - Specifies a body that should be sent with `HTTP` checks.
- `Header` `(map[string][]string: {})` - Specifies a set of headers that should
be set for `HTTP` checks. Each header can have multiple values.
@ -221,7 +223,8 @@ The table below shows this endpoint's support for
"Shell": "/bin/bash",
"HTTP": "https://example.com",
"Method": "POST",
"Header": {"x-foo":["bar", "baz"]},
"Header": {"Content-Type": "application/json"},
"Body": "{\"check\":\"mem\"}",
"TCP": "example.com:22",
"Interval": "10s",
"Timeout": "5s",

View File

@ -156,7 +156,8 @@ A HTTP check:
"http": "https://localhost:5000/health",
"tls_skip_verify": false,
"method": "POST",
"header": {"x-foo":["bar", "baz"]},
"header": {"Content-Type": "application/json"},
"body": "{\"method\":\"health\"}",
"interval": "10s",
"timeout": "1s"
}