Add result related tags and fields to http_response (#3814)
This commit is contained in:
parent
8005883de8
commit
81a93fcddf
|
@ -44,18 +44,38 @@ This input plugin will test HTTP/HTTPS connections.
|
||||||
### Measurements & Fields:
|
### Measurements & Fields:
|
||||||
|
|
||||||
- http_response
|
- http_response
|
||||||
- response_time (float, seconds)
|
- response_time (float, seconds) # Not set if target is unreachable for any reason
|
||||||
- http_response_code (int) #The code received
|
- http_response_code (int) # The HTTP code received
|
||||||
- result_type (string) # success, timeout, response_string_mismatch, connection_failed
|
- result_type (string) # Legacy field mantained for backwards compatibility
|
||||||
|
- result_code (int) # Details [here](#result-tag-and-result_code-field)
|
||||||
|
|
||||||
|
|
||||||
### Tags:
|
### Tags:
|
||||||
|
|
||||||
- All measurements have the following tags:
|
- All measurements have the following tags:
|
||||||
- server
|
- server # Server URL used
|
||||||
- method
|
- 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:
|
### 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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -133,10 +135,54 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
|
||||||
return client, nil
|
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
|
// HTTPGather gathers all fields and returns any errors it encounters
|
||||||
func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
func (h *HTTPResponse) httpGather() (map[string]interface{}, map[string]string, error) {
|
||||||
// Prepare fields
|
// Prepare fields and tags
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
|
tags := map[string]string{"server": h.Address, "method": h.Method}
|
||||||
|
|
||||||
var body io.Reader
|
var body io.Reader
|
||||||
if h.Body != "" {
|
if h.Body != "" {
|
||||||
|
@ -144,7 +190,7 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
request, err := http.NewRequest(h.Method, h.Address, body)
|
request, err := http.NewRequest(h.Method, h.Address, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, val := range h.Headers {
|
for key, val := range h.Headers {
|
||||||
|
@ -157,68 +203,87 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
|
||||||
// Start Timer
|
// Start Timer
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := h.client.Do(request)
|
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 err != nil {
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
// Log error
|
||||||
fields["result_type"] = "timeout"
|
log.Printf("D! Network error while polling %s: %s", h.Address, err.Error())
|
||||||
return fields, nil
|
|
||||||
|
// 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 {
|
// Any error not recognized by `set_error` is considered a "connection_failed"
|
||||||
return fields, nil
|
setResult("connection_failed", fields, tags)
|
||||||
}
|
|
||||||
if urlError, ok := err.(*url.Error); ok &&
|
// If the error is a redirect we continue processing and log the HTTP code
|
||||||
urlError.Err == ErrRedirectAttempted {
|
urlError, isUrlError := err.(*url.Error)
|
||||||
|
if !h.FollowRedirects && isUrlError && urlError.Err == ErrRedirectAttempted {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} 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() {
|
defer func() {
|
||||||
io.Copy(ioutil.Discard, resp.Body)
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
resp.Body.Close()
|
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
|
fields["http_response_code"] = resp.StatusCode
|
||||||
|
|
||||||
// Check the response for a regex match.
|
// Check the response for a regex match.
|
||||||
if h.ResponseStringMatch != "" {
|
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)
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("E! Failed to read body of HTTP Response : %s", err)
|
log.Printf("D! Failed to read body of HTTP Response : %s", err)
|
||||||
fields["result_type"] = "response_string_mismatch"
|
setResult("body_read_error", fields, tags)
|
||||||
fields["response_string_match"] = 0
|
fields["response_string_match"] = 0
|
||||||
return fields, nil
|
return fields, tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.compiledStringMatch.Match(bodyBytes) {
|
if h.compiledStringMatch.Match(bodyBytes) {
|
||||||
fields["result_type"] = "success"
|
setResult("success", fields, tags)
|
||||||
fields["response_string_match"] = 1
|
fields["response_string_match"] = 1
|
||||||
} else {
|
} else {
|
||||||
fields["result_type"] = "response_string_mismatch"
|
setResult("response_string_mismatch", fields, tags)
|
||||||
fields["response_string_match"] = 0
|
fields["response_string_match"] = 0
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Gather gets all metric fields and tags and returns any errors it encounters
|
||||||
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
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
|
// Set default values
|
||||||
if h.ResponseTimeout.Duration < time.Second {
|
if h.ResponseTimeout.Duration < time.Second {
|
||||||
h.ResponseTimeout.Duration = time.Second * 5
|
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" {
|
if addr.Scheme != "http" && addr.Scheme != "https" {
|
||||||
return errors.New("Only http and https are supported")
|
return errors.New("Only http and https are supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare data
|
// Prepare data
|
||||||
tags := map[string]string{"server": h.Address, "method": h.Method}
|
|
||||||
var fields map[string]interface{}
|
var fields map[string]interface{}
|
||||||
|
var tags map[string]string
|
||||||
|
|
||||||
if h.client == nil {
|
if h.client == nil {
|
||||||
client, err := h.createHttpClient()
|
client, err := h.createHttpClient()
|
||||||
|
@ -250,10 +316,11 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather data
|
// Gather data
|
||||||
fields, err = h.httpGather()
|
fields, tags, err = h.httpGather()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add metrics
|
// Add metrics
|
||||||
acc.AddFields("http_response", fields, tags)
|
acc.AddFields("http_response", fields, tags)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,6 +15,68 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"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 {
|
func setUpTestMux() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -56,6 +118,24 @@ func setUpTestMux() http.Handler {
|
||||||
return mux
|
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) {
|
func TestHeaders(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
cHeader := r.Header.Get("Content-Type")
|
cHeader := r.Header.Get("Content-Type")
|
||||||
|
@ -78,9 +158,20 @@ func TestHeaders(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"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) {
|
func TestFields(t *testing.T) {
|
||||||
|
@ -103,12 +194,20 @@ func TestFields(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"result_type": "success",
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
"result_code": 0,
|
||||||
require.True(t, ok)
|
"response_time": nil,
|
||||||
require.Equal(t, "success", response_value)
|
}
|
||||||
|
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) {
|
func TestRedirects(t *testing.T) {
|
||||||
|
@ -130,9 +229,20 @@ func TestRedirects(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"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{
|
h = &HTTPResponse{
|
||||||
Address: ts.URL + "/badredirect",
|
Address: ts.URL + "/badredirect",
|
||||||
|
@ -148,11 +258,21 @@ func TestRedirects(t *testing.T) {
|
||||||
err = h.Gather(&acc)
|
err = h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok = acc.IntField("http_response", "http_response_code")
|
expectedFields = map[string]interface{}{
|
||||||
require.False(t, ok)
|
"result_type": "connection_failed",
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
"result_code": 3,
|
||||||
require.True(t, ok)
|
}
|
||||||
require.Equal(t, "connection_failed", response_value)
|
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) {
|
func TestMethod(t *testing.T) {
|
||||||
|
@ -174,9 +294,20 @@ func TestMethod(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"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{
|
h = &HTTPResponse{
|
||||||
Address: ts.URL + "/mustbepostmethod",
|
Address: ts.URL + "/mustbepostmethod",
|
||||||
|
@ -192,9 +323,20 @@ func TestMethod(t *testing.T) {
|
||||||
err = h.Gather(&acc)
|
err = h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok = acc.IntField("http_response", "http_response_code")
|
expectedFields = map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusMethodNotAllowed,
|
||||||
require.Equal(t, http.StatusMethodNotAllowed, value)
|
"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
|
//check that lowercase methods work correctly
|
||||||
h = &HTTPResponse{
|
h = &HTTPResponse{
|
||||||
|
@ -211,9 +353,20 @@ func TestMethod(t *testing.T) {
|
||||||
err = h.Gather(&acc)
|
err = h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok = acc.IntField("http_response", "http_response_code")
|
expectedFields = map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusMethodNotAllowed,
|
||||||
require.Equal(t, http.StatusMethodNotAllowed, value)
|
"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) {
|
func TestBody(t *testing.T) {
|
||||||
|
@ -235,9 +388,20 @@ func TestBody(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"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{
|
h = &HTTPResponse{
|
||||||
Address: ts.URL + "/musthaveabody",
|
Address: ts.URL + "/musthaveabody",
|
||||||
|
@ -252,9 +416,19 @@ func TestBody(t *testing.T) {
|
||||||
err = h.Gather(&acc)
|
err = h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok = acc.IntField("http_response", "http_response_code")
|
expectedFields = map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusBadRequest,
|
||||||
require.Equal(t, http.StatusBadRequest, value)
|
"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) {
|
func TestStringMatch(t *testing.T) {
|
||||||
|
@ -277,17 +451,20 @@ func TestStringMatch(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"response_string_match": 1,
|
||||||
value, ok = acc.IntField("http_response", "response_string_match")
|
"result_type": "success",
|
||||||
require.True(t, ok)
|
"result_code": 0,
|
||||||
require.Equal(t, 1, value)
|
"response_time": nil,
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
}
|
||||||
require.True(t, ok)
|
expectedTags := map[string]interface{}{
|
||||||
require.Equal(t, "success", response_value)
|
"server": nil,
|
||||||
_, ok = acc.FloatField("http_response", "response_time")
|
"method": "GET",
|
||||||
require.True(t, ok)
|
"status_code": "200",
|
||||||
|
"result": "success",
|
||||||
|
}
|
||||||
|
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringMatchJson(t *testing.T) {
|
func TestStringMatchJson(t *testing.T) {
|
||||||
|
@ -310,17 +487,20 @@ func TestStringMatchJson(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"response_string_match": 1,
|
||||||
value, ok = acc.IntField("http_response", "response_string_match")
|
"result_type": "success",
|
||||||
require.True(t, ok)
|
"result_code": 0,
|
||||||
require.Equal(t, 1, value)
|
"response_time": nil,
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
}
|
||||||
require.True(t, ok)
|
expectedTags := map[string]interface{}{
|
||||||
require.Equal(t, "success", response_value)
|
"server": nil,
|
||||||
_, ok = acc.FloatField("http_response", "response_time")
|
"method": "GET",
|
||||||
require.True(t, ok)
|
"status_code": "200",
|
||||||
|
"result": "success",
|
||||||
|
}
|
||||||
|
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringMatchFail(t *testing.T) {
|
func TestStringMatchFail(t *testing.T) {
|
||||||
|
@ -344,17 +524,20 @@ func TestStringMatchFail(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
value, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.True(t, ok)
|
"http_response_code": http.StatusOK,
|
||||||
require.Equal(t, http.StatusOK, value)
|
"response_string_match": 0,
|
||||||
value, ok = acc.IntField("http_response", "response_string_match")
|
"result_type": "response_string_mismatch",
|
||||||
require.True(t, ok)
|
"result_code": 1,
|
||||||
require.Equal(t, 0, value)
|
"response_time": nil,
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
}
|
||||||
require.True(t, ok)
|
expectedTags := map[string]interface{}{
|
||||||
require.Equal(t, "response_string_mismatch", response_value)
|
"server": nil,
|
||||||
_, ok = acc.FloatField("http_response", "response_time")
|
"method": "GET",
|
||||||
require.True(t, ok)
|
"status_code": "200",
|
||||||
|
"result": "response_string_mismatch",
|
||||||
|
}
|
||||||
|
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
func TestTimeout(t *testing.T) {
|
||||||
|
@ -380,11 +563,126 @@ func TestTimeout(t *testing.T) {
|
||||||
err := h.Gather(&acc)
|
err := h.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, ok := acc.IntField("http_response", "http_response_code")
|
expectedFields := map[string]interface{}{
|
||||||
require.False(t, ok)
|
"result_type": "timeout",
|
||||||
response_value, ok := acc.StringField("http_response", "result_type")
|
"result_code": 4,
|
||||||
require.True(t, ok)
|
}
|
||||||
require.Equal(t, "timeout", response_value)
|
expectedTags := map[string]interface{}{
|
||||||
_, ok = acc.FloatField("http_response", "response_time")
|
"server": nil,
|
||||||
require.False(t, ok)
|
"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