Add result related tags and fields to http_response (#3814)
This commit is contained in:
parent
fe78df3ba0
commit
e9da4e529e
|
@ -44,18 +44,38 @@ This input plugin will test HTTP/HTTPS connections.
|
|||
### Measurements & Fields:
|
||||
|
||||
- http_response
|
||||
- response_time (float, seconds)
|
||||
- http_response_code (int) #The code received
|
||||
- result_type (string) # success, timeout, response_string_mismatch, connection_failed
|
||||
- response_time (float, seconds) # Not set if target is unreachable for any reason
|
||||
- http_response_code (int) # The HTTP code received
|
||||
- result_type (string) # Legacy field mantained for backwards compatibility
|
||||
- result_code (int) # Details [here](#result-tag-and-result_code-field)
|
||||
|
||||
|
||||
### Tags:
|
||||
|
||||
- All measurements have the following tags:
|
||||
- server
|
||||
- method
|
||||
- server # Server URL used
|
||||
- method # HTTP method used (GET, POST, PUT, etc)
|
||||
- status_code # String with the HTTP status code
|
||||
- result # Details [here](#result-tag-and-result_code-field)
|
||||
|
||||
### Result tag and Result_code field
|
||||
Upon finishing polling the target server, the plugin registers the result of the operation in the `result` tag, and adds a numeric field called `result_code` corresponding with that tag value.
|
||||
|
||||
This tag is used to expose network and plugin errors. HTTP errors are considered a sucessful connection by the plugin.
|
||||
|
||||
|Tag value |Corresponding field value|Description|
|
||||
--------------------------|-------------------------|-----------|
|
||||
|success | 0 |The HTTP request completed, even if the HTTP code represents an error|
|
||||
|response_string_mismatch | 1 |The option `response_string_match` was used, and the body of the response didn't match the regex|
|
||||
|body_read_error | 2 |The option `response_string_match` was used, but the plugin wans't able to read the body of the response. Responses with empty bodies (like 3xx, HEAD, etc) will trigger this error|
|
||||
|connection_failed | 3 |Catch all for any network error not specifically handled by the plugin|
|
||||
|timeout | 4 |The plugin timed out while awaiting the HTTP connection to complete|
|
||||
|dns_error | 5 |There was a DNS error while attempting to connect to the host|
|
||||
|
||||
NOTE: The error codes are derived from the error object returned by the `net/http` Go library, so the accuracy of the errors depends on the handling of error states by the `net/http` Go library. **If a more detailed error report is required use the `log_network_errors` setting.**
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
http_response,method=GET,server=http://www.github.com http_response_code=200i,response_time=6.223266528 1459419354977857955
|
||||
http_response,method=GET,server=http://www.github.com,status_code="200",result="success" http_response_code=200i,response_time=6.223266528,result_type="sucess",result_code=0i 1459419354977857955
|
||||
```
|
||||
|
|
|
@ -2,6 +2,7 @@ package http_response
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -133,10 +135,54 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
|
|||
return client, nil
|
||||
}
|
||||
|
||||
func setResult(result_string string, fields map[string]interface{}, tags map[string]string) {
|
||||
result_codes := map[string]int{
|
||||
"success": 0,
|
||||
"response_string_mismatch": 1,
|
||||
"body_read_error": 2,
|
||||
"connection_failed": 3,
|
||||
"timeout": 4,
|
||||
"dns_error": 5,
|
||||
}
|
||||
|
||||
tags["result"] = result_string
|
||||
fields["result_type"] = result_string
|
||||
fields["result_code"] = result_codes[result_string]
|
||||
}
|
||||
|
||||
func setError(err error, fields map[string]interface{}, tags map[string]string) error {
|
||||
if timeoutError, ok := err.(net.Error); ok && timeoutError.Timeout() {
|
||||
setResult("timeout", fields, tags)
|
||||
return timeoutError
|
||||
}
|
||||
|
||||
urlErr, isUrlErr := err.(*url.Error)
|
||||
if !isUrlErr {
|
||||
return nil
|
||||
}
|
||||
|
||||
opErr, isNetErr := (urlErr.Err).(*net.OpError)
|
||||
if isNetErr {
|
||||
switch e := (opErr.Err).(type) {
|
||||
case (*net.DNSError):
|
||||
setResult("dns_error", fields, tags)
|
||||
return e
|
||||
case (*net.ParseError):
|
||||
// Parse error has to do with parsing of IP addresses, so we
|
||||
// group it with address errors
|
||||
setResult("address_error", fields, tags)
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HTTPGather gathers all fields and returns any errors it encounters
|
||||
func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
||||
// Prepare fields
|
||||
func (h *HTTPResponse) httpGather() (map[string]interface{}, map[string]string, error) {
|
||||
// Prepare fields and tags
|
||||
fields := make(map[string]interface{})
|
||||
tags := map[string]string{"server": h.Address, "method": h.Method}
|
||||
|
||||
var body io.Reader
|
||||
if h.Body != "" {
|
||||
|
@ -144,7 +190,7 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
|||
}
|
||||
request, err := http.NewRequest(h.Method, h.Address, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for key, val := range h.Headers {
|
||||
|
@ -157,68 +203,87 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
|||
// Start Timer
|
||||
start := time.Now()
|
||||
resp, err := h.client.Do(request)
|
||||
response_time := time.Since(start).Seconds()
|
||||
|
||||
// If an error in returned, it means we are dealing with a network error, as
|
||||
// HTTP error codes do not generate errors in the net/http library
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
fields["result_type"] = "timeout"
|
||||
return fields, nil
|
||||
// Log error
|
||||
log.Printf("D! Network error while polling %s: %s", h.Address, err.Error())
|
||||
|
||||
// Get error details
|
||||
netErr := setError(err, fields, tags)
|
||||
|
||||
// If recognize the returnded error, get out
|
||||
if netErr != nil {
|
||||
return fields, tags, nil
|
||||
}
|
||||
fields["result_type"] = "connection_failed"
|
||||
if h.FollowRedirects {
|
||||
return fields, nil
|
||||
}
|
||||
if urlError, ok := err.(*url.Error); ok &&
|
||||
urlError.Err == ErrRedirectAttempted {
|
||||
|
||||
// Any error not recognized by `set_error` is considered a "connection_failed"
|
||||
setResult("connection_failed", fields, tags)
|
||||
|
||||
// If the error is a redirect we continue processing and log the HTTP code
|
||||
urlError, isUrlError := err.(*url.Error)
|
||||
if !h.FollowRedirects && isUrlError && urlError.Err == ErrRedirectAttempted {
|
||||
err = nil
|
||||
} else {
|
||||
return fields, nil
|
||||
// If the error isn't a timeout or a redirect stop
|
||||
// processing the request
|
||||
return fields, tags, nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := fields["response_time"]; !ok {
|
||||
fields["response_time"] = response_time
|
||||
}
|
||||
|
||||
// This function closes the response body, as
|
||||
// required by the net/http library
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
fields["response_time"] = time.Since(start).Seconds()
|
||||
// Set log the HTTP response code
|
||||
tags["status_code"] = strconv.Itoa(resp.StatusCode)
|
||||
fields["http_response_code"] = resp.StatusCode
|
||||
|
||||
// Check the response for a regex match.
|
||||
if h.ResponseStringMatch != "" {
|
||||
|
||||
// Compile once and reuse
|
||||
if h.compiledStringMatch == nil {
|
||||
h.compiledStringMatch = regexp.MustCompile(h.ResponseStringMatch)
|
||||
if err != nil {
|
||||
log.Printf("E! Failed to compile regular expression %s : %s", h.ResponseStringMatch, err)
|
||||
fields["result_type"] = "response_string_mismatch"
|
||||
return fields, nil
|
||||
}
|
||||
}
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("E! Failed to read body of HTTP Response : %s", err)
|
||||
fields["result_type"] = "response_string_mismatch"
|
||||
log.Printf("D! Failed to read body of HTTP Response : %s", err)
|
||||
setResult("body_read_error", fields, tags)
|
||||
fields["response_string_match"] = 0
|
||||
return fields, nil
|
||||
return fields, tags, nil
|
||||
}
|
||||
|
||||
if h.compiledStringMatch.Match(bodyBytes) {
|
||||
fields["result_type"] = "success"
|
||||
setResult("success", fields, tags)
|
||||
fields["response_string_match"] = 1
|
||||
} else {
|
||||
fields["result_type"] = "response_string_mismatch"
|
||||
setResult("response_string_mismatch", fields, tags)
|
||||
fields["response_string_match"] = 0
|
||||
}
|
||||
} else {
|
||||
fields["result_type"] = "success"
|
||||
setResult("success", fields, tags)
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
return fields, tags, nil
|
||||
}
|
||||
|
||||
// Gather gets all metric fields and tags and returns any errors it encounters
|
||||
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
||||
// Compile the body regex if it exist
|
||||
if h.compiledStringMatch == nil {
|
||||
var err error
|
||||
h.compiledStringMatch, err = regexp.Compile(h.ResponseStringMatch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to compile regular expression %s : %s", h.ResponseStringMatch, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set default values
|
||||
if h.ResponseTimeout.Duration < time.Second {
|
||||
h.ResponseTimeout.Duration = time.Second * 5
|
||||
|
@ -237,9 +302,10 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
|||
if addr.Scheme != "http" && addr.Scheme != "https" {
|
||||
return errors.New("Only http and https are supported")
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
tags := map[string]string{"server": h.Address, "method": h.Method}
|
||||
var fields map[string]interface{}
|
||||
var tags map[string]string
|
||||
|
||||
if h.client == nil {
|
||||
client, err := h.createHttpClient()
|
||||
|
@ -250,10 +316,11 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
|||
}
|
||||
|
||||
// Gather data
|
||||
fields, err = h.httpGather()
|
||||
fields, tags, err = h.httpGather()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add metrics
|
||||
acc.AddFields("http_response", fields, tags)
|
||||
return nil
|
||||
|
|
|
@ -15,6 +15,68 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Receives a list with fields that are expected to be absent
|
||||
func checkAbsentFields(t *testing.T, fields []string, acc testutil.Accumulator) {
|
||||
for _, field := range fields {
|
||||
ok := acc.HasField("http_response", field)
|
||||
require.False(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Receives a list with tags that are expected to be absent
|
||||
func checkAbsentTags(t *testing.T, tags []string, acc testutil.Accumulator) {
|
||||
for _, tag := range tags {
|
||||
ok := acc.HasTag("http_response", tag)
|
||||
require.False(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Receives a dictionary and with expected fields and their values. If a value is nil, it will only check
|
||||
// that the field exists, but not its contents
|
||||
func checkFields(t *testing.T, fields map[string]interface{}, acc testutil.Accumulator) {
|
||||
for key, field := range fields {
|
||||
switch v := field.(type) {
|
||||
case int:
|
||||
value, ok := acc.IntField("http_response", key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, field, value)
|
||||
case float64:
|
||||
value, ok := acc.FloatField("http_response", key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, field, value)
|
||||
case string:
|
||||
value, ok := acc.StringField("http_response", key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, field, value)
|
||||
case nil:
|
||||
ok := acc.HasField("http_response", key)
|
||||
require.True(t, ok)
|
||||
default:
|
||||
t.Log("Unsupported type for field: ", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receives a dictionary and with expected tags and their values. If a value is nil, it will only check
|
||||
// that the tag exists, but not its contents
|
||||
func checkTags(t *testing.T, tags map[string]interface{}, acc testutil.Accumulator) {
|
||||
for key, tag := range tags {
|
||||
switch v := tag.(type) {
|
||||
case string:
|
||||
ok := acc.HasTag("http_response", key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tag, acc.TagValue("http_response", key))
|
||||
case nil:
|
||||
ok := acc.HasTag("http_response", key)
|
||||
require.True(t, ok)
|
||||
default:
|
||||
t.Log("Unsupported type for tag: ", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setUpTestMux() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -56,6 +118,24 @@ func setUpTestMux() http.Handler {
|
|||
return mux
|
||||
}
|
||||
|
||||
func checkOutput(t *testing.T, acc testutil.Accumulator, presentFields map[string]interface{}, presentTags map[string]interface{}, absentFields []string, absentTags []string) {
|
||||
if presentFields != nil {
|
||||
checkFields(t, presentFields, acc)
|
||||
}
|
||||
|
||||
if presentTags != nil {
|
||||
checkTags(t, presentTags, acc)
|
||||
}
|
||||
|
||||
if absentFields != nil {
|
||||
checkAbsentFields(t, absentFields, acc)
|
||||
}
|
||||
|
||||
if absentTags != nil {
|
||||
checkAbsentTags(t, absentTags, acc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaders(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cHeader := r.Header.Get("Content-Type")
|
||||
|
@ -78,9 +158,20 @@ func TestHeaders(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields := []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
|
@ -103,12 +194,20 @@ func TestFields(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "success", response_value)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields := []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
}
|
||||
|
||||
func TestRedirects(t *testing.T) {
|
||||
|
@ -130,9 +229,20 @@ func TestRedirects(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields := []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/badredirect",
|
||||
|
@ -148,11 +258,21 @@ func TestRedirects(t *testing.T) {
|
|||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok = acc.IntField("http_response", "http_response_code")
|
||||
require.False(t, ok)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "connection_failed", response_value)
|
||||
expectedFields = map[string]interface{}{
|
||||
"result_type": "connection_failed",
|
||||
"result_code": 3,
|
||||
}
|
||||
expectedTags = map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"result": "connection_failed",
|
||||
}
|
||||
absentFields = []string{"http_response_code", "response_time", "response_string_match"}
|
||||
absentTags := []string{"status_code"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||
|
||||
expectedFields = map[string]interface{}{"result_type": "connection_failed"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
|
||||
}
|
||||
|
||||
func TestMethod(t *testing.T) {
|
||||
|
@ -174,9 +294,20 @@ func TestMethod(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "POST",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields := []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
|
@ -192,9 +323,20 @@ func TestMethod(t *testing.T) {
|
|||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok = acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusMethodNotAllowed, value)
|
||||
expectedFields = map[string]interface{}{
|
||||
"http_response_code": http.StatusMethodNotAllowed,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags = map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "405",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields = []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
|
||||
//check that lowercase methods work correctly
|
||||
h = &HTTPResponse{
|
||||
|
@ -211,9 +353,20 @@ func TestMethod(t *testing.T) {
|
|||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok = acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusMethodNotAllowed, value)
|
||||
expectedFields = map[string]interface{}{
|
||||
"http_response_code": http.StatusMethodNotAllowed,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags = map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "head",
|
||||
"status_code": "405",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields = []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
}
|
||||
|
||||
func TestBody(t *testing.T) {
|
||||
|
@ -235,9 +388,20 @@ func TestBody(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields := []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/musthaveabody",
|
||||
|
@ -252,9 +416,19 @@ func TestBody(t *testing.T) {
|
|||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok = acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusBadRequest, value)
|
||||
expectedFields = map[string]interface{}{
|
||||
"http_response_code": http.StatusBadRequest,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
}
|
||||
expectedTags = map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "400",
|
||||
"result": "success",
|
||||
}
|
||||
absentFields = []string{"response_string_match"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
|
||||
}
|
||||
|
||||
func TestStringMatch(t *testing.T) {
|
||||
|
@ -277,17 +451,20 @@ func TestStringMatch(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
value, ok = acc.IntField("http_response", "response_string_match")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 1, value)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "success", response_value)
|
||||
_, ok = acc.FloatField("http_response", "response_time")
|
||||
require.True(t, ok)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"response_string_match": 1,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||
}
|
||||
|
||||
func TestStringMatchJson(t *testing.T) {
|
||||
|
@ -310,17 +487,20 @@ func TestStringMatchJson(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
value, ok = acc.IntField("http_response", "response_string_match")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 1, value)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "success", response_value)
|
||||
_, ok = acc.FloatField("http_response", "response_time")
|
||||
require.True(t, ok)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"response_string_match": 1,
|
||||
"result_type": "success",
|
||||
"result_code": 0,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "success",
|
||||
}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||
}
|
||||
|
||||
func TestStringMatchFail(t *testing.T) {
|
||||
|
@ -344,17 +524,20 @@ func TestStringMatchFail(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, value)
|
||||
value, ok = acc.IntField("http_response", "response_string_match")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 0, value)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "response_string_mismatch", response_value)
|
||||
_, ok = acc.FloatField("http_response", "response_time")
|
||||
require.True(t, ok)
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusOK,
|
||||
"response_string_match": 0,
|
||||
"result_type": "response_string_mismatch",
|
||||
"result_code": 1,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "200",
|
||||
"result": "response_string_mismatch",
|
||||
}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
|
@ -380,11 +563,126 @@ func TestTimeout(t *testing.T) {
|
|||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := acc.IntField("http_response", "http_response_code")
|
||||
require.False(t, ok)
|
||||
response_value, ok := acc.StringField("http_response", "result_type")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "timeout", response_value)
|
||||
_, ok = acc.FloatField("http_response", "response_time")
|
||||
require.False(t, ok)
|
||||
expectedFields := map[string]interface{}{
|
||||
"result_type": "timeout",
|
||||
"result_code": 4,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"result": "timeout",
|
||||
}
|
||||
absentFields := []string{"http_response_code", "response_time", "response_string_match"}
|
||||
absentTags := []string{"status_code"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
|
||||
}
|
||||
|
||||
func TestPluginErrors(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
// Bad regex test. Should return an error and return nothing
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/good",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseStringMatch: "bad regex:[[",
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := h.Gather(&acc)
|
||||
require.Error(t, err)
|
||||
|
||||
absentFields := []string{"http_response_code", "response_time", "response_string_match", "result_type", "result_code"}
|
||||
absentTags := []string{"status_code", "result", "server", "method"}
|
||||
checkOutput(t, acc, nil, nil, absentFields, absentTags)
|
||||
|
||||
// Attempt to read empty body test
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/redirect",
|
||||
Body: "",
|
||||
Method: "GET",
|
||||
ResponseStringMatch: ".*",
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
FollowRedirects: false,
|
||||
}
|
||||
|
||||
acc = testutil.Accumulator{}
|
||||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedFields := map[string]interface{}{
|
||||
"http_response_code": http.StatusMovedPermanently,
|
||||
"response_string_match": 0,
|
||||
"result_type": "body_read_error",
|
||||
"result_code": 2,
|
||||
"response_time": nil,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"status_code": "301",
|
||||
"result": "body_read_error",
|
||||
}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||
}
|
||||
|
||||
func TestNetworkErrors(t *testing.T) {
|
||||
// DNS error
|
||||
h := &HTTPResponse{
|
||||
Address: "https://nonexistent.nonexistent", // Any non-resolvable URL works here
|
||||
Body: "",
|
||||
Method: "GET",
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
FollowRedirects: false,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedFields := map[string]interface{}{
|
||||
"result_type": "dns_error",
|
||||
"result_code": 5,
|
||||
}
|
||||
expectedTags := map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"result": "dns_error",
|
||||
}
|
||||
absentFields := []string{"http_response_code", "response_time", "response_string_match"}
|
||||
absentTags := []string{"status_code"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
|
||||
|
||||
// Connecton failed
|
||||
h = &HTTPResponse{
|
||||
Address: "https://127.127.127.127", // Any non-routable IP works here
|
||||
Body: "",
|
||||
Method: "GET",
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
FollowRedirects: false,
|
||||
}
|
||||
|
||||
acc = testutil.Accumulator{}
|
||||
err = h.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedFields = map[string]interface{}{
|
||||
"result_type": "connection_failed",
|
||||
"result_code": 3,
|
||||
}
|
||||
expectedTags = map[string]interface{}{
|
||||
"server": nil,
|
||||
"method": "GET",
|
||||
"result": "connection_failed",
|
||||
}
|
||||
absentFields = []string{"http_response_code", "response_time", "response_string_match"}
|
||||
absentTags = []string{"status_code"}
|
||||
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue