Skip to content

Add support for interface field in http_response input plugin #6006

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

Merged
merged 4 commits into from
Jun 19, 2019
Merged
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
3 changes: 3 additions & 0 deletions plugins/inputs/http_response/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ This input plugin checks HTTP/HTTPS connections.
## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
# Host = "github.com"

## Interface to use when dialing an address
# interface = "eth0"
```

### Metrics:
Expand Down
38 changes: 37 additions & 1 deletion plugins/inputs/http_response/http_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type HTTPResponse struct {
Headers map[string]string
FollowRedirects bool
ResponseStringMatch string
Interface string
tls.ClientConfig

compiledStringMatch *regexp.Regexp
Expand Down Expand Up @@ -82,6 +83,9 @@ var sampleConfig = `
## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
# Host = "github.com"

## Interface to use when dialing an address
# interface = "eth0"
`

// SampleConfig returns the plugin SampleConfig
Expand All @@ -108,16 +112,27 @@ func getProxyFunc(http_proxy string) func(*http.Request) (*url.URL, error) {
}
}

// CreateHttpClient creates an http client which will timeout at the specified
// createHttpClient creates an http client which will timeout at the specified
// timeout period and can follow redirects if specified
func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
tlsCfg, err := h.ClientConfig.TLSConfig()
if err != nil {
return nil, err
}

dialer := &net.Dialer{}

if h.Interface != "" {
dialer.LocalAddr, err = localAddress(h.Interface)
if err != nil {
return nil, err
}
}

client := &http.Client{
Transport: &http.Transport{
Proxy: getProxyFunc(h.HTTPProxy),
DialContext: dialer.DialContext,
DisableKeepAlives: true,
TLSClientConfig: tlsCfg,
},
Expand All @@ -132,6 +147,27 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
return client, nil
}

func localAddress(interfaceName string) (net.Addr, error) {
i, err := net.InterfaceByName(interfaceName)
if err != nil {
return nil, err
}

addrs, err := i.Addrs()
if err != nil {
return nil, err
}

for _, addr := range addrs {
if naddr, ok := addr.(*net.IPNet); ok {
// leaving port set to zero to let kernel pick
return &net.TCPAddr{IP: naddr.IP}, nil
}
}

return nil, fmt.Errorf("cannot create local address for interface %q", interfaceName)
}

func setResult(result_string string, fields map[string]interface{}, tags map[string]string) {
result_codes := map[string]int{
"success": 0,
Expand Down
63 changes: 63 additions & 0 deletions plugins/inputs/http_response/http_response_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package http_response

import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -210,6 +212,67 @@ func TestFields(t *testing.T) {
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
}

func findInterface() (net.Interface, error) {
potential, _ := net.Interfaces()

for _, i := range potential {
// we are only interest in loopback interfaces which are up
if (i.Flags&net.FlagUp == 0) || (i.Flags&net.FlagLoopback == 0) {
continue
}

if addrs, _ := i.Addrs(); len(addrs) > 0 {
// return interface if it has at least one unicast address
return i, nil
}
}

return net.Interface{}, errors.New("cannot find suitable loopback interface")
}

func TestInterface(t *testing.T) {
var (
mux = setUpTestMux()
ts = httptest.NewServer(mux)
)

defer ts.Close()

intf, err := findInterface()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the test would depend on finding an interface that can route to the test http server network, which would only be a loopback interface because this server listens on 127.0.0.1:0. Could we simplify this function to just grab any loopback interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. That makes more sense. I was trying to test a non-loopback interface, which in hindsight makes no sense. I will make it get the name of the first loopback interface it finds.

require.NoError(t, err)

h := &HTTPResponse{
Address: ts.URL + "/good",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"Content-Type": "application/json",
},
FollowRedirects: true,
Interface: intf.Name,
}

var acc testutil.Accumulator
err = h.Gather(&acc)
require.NoError(t, err)

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) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
Expand Down