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

Add support for interface field in http_response input plugin #6006

Merged
merged 4 commits into from
Jun 19, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
65 changes: 65 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,69 @@ func TestFields(t *testing.T) {
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
}

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

for _, i := range potential {
// ignore interfaces which are down or not used for broadcast
if (i.Flags&net.FlagUp == 0) || (i.Flags&net.FlagBroadcast == 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 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.

if err != nil {
t.Fatal(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

I likely would have just t.Skip(err), but then again I believe httptest.NewServer requires at least the loopback interface, so this is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can change that if skip is preferred. I didn’t realize skip existed in that form.

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine, since we require the loopback in many tests, though in Telegraf we usually use testify: 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