From 5953db88df68d89235cebe121371160346f9559d Mon Sep 17 00:00:00 2001
From: Randy Coburn <morfien101@gmail.com>
Date: Wed, 9 May 2018 00:07:15 +0100
Subject: [PATCH] Add tag/integer pair for result to net_response (#3455)

---
 plugins/inputs/net_response/README.md         |  20 ++--
 plugins/inputs/net_response/net_response.go   | 101 ++++++++++++-----
 .../inputs/net_response/net_response_test.go  | 104 +++++++++++++++---
 3 files changed, 170 insertions(+), 55 deletions(-)

diff --git a/plugins/inputs/net_response/README.md b/plugins/inputs/net_response/README.md
index 01c681b5..50dbc9f8 100644
--- a/plugins/inputs/net_response/README.md
+++ b/plugins/inputs/net_response/README.md
@@ -59,7 +59,9 @@ It can also check response text.
 
 - net_response
     - response_time (float, seconds)
-    - result_type (string) # success, timeout, connection_failed, read_failed, string_mismatch
+    - success (int) # success 0, failure 1
+    - result_code (int) # success = 0, Failure = 1
+    - [**DEPRECATED**] result_type (string) # success, timeout, connection_failed, read_failed, string_mismatch
     - [**DEPRECATED**] string_found (boolean)
 
 ### Tags:
@@ -68,16 +70,16 @@ It can also check response text.
     - server
     - port
     - protocol
+    - result_text (string) # This will contain the text from the now deprecated result_type
 
 ### Example Output:
 
 ```
-$ ./telegraf --config telegraf.conf --input-filter net_response --test
-net_response,server=influxdata.com,port=8080,protocol=tcp,host=localhost result_type="timeout" 1499310361000000000
-net_response,server=influxdata.com,port=443,protocol=tcp,host=localhost result_type="success",response_time=0.088703864 1499310361000000000
-net_response,protocol=tcp,host=localhost,server=this.domain.does.not.exist,port=443 result_type="connection_failed" 1499310361000000000
-net_response,protocol=udp,host=localhost,server=influxdata.com,port=8080 result_type="read_failed" 1499310362000000000
-net_response,port=31338,protocol=udp,host=localhost,server=localhost result_type="string_mismatch",string_found=false,response_time=0.00242682 1499310362000000000
-net_response,protocol=udp,host=localhost,server=localhost,port=31338 response_time=0.001128598,result_type="success",string_found=true 1499310362000000000
-net_response,server=this.domain.does.not.exist,port=443,protocol=udp,host=localhost result_type="connection_failed" 1499310362000000000
+net_response,server=influxdata.com,port=8080,protocol=tcp,host=localhost,result_text="timeout" result_code=1i,result_type="timeout" 1499310361000000000
+net_response,server=influxdata.com,port=443,protocol=tcp,host=localhost,result_text="success" result_code=0i,result_type="success",response_time=0.088703864 1499310361000000000
+net_response,protocol=tcp,host=localhost,server=this.domain.does.not.exist,port=443,result_text="connection_failed" result_code=1i,result_type="connection_failed" 1499310361000000000
+net_response,protocol=udp,host=localhost,server=influxdata.com,port=8080,result_text="read_failed" result_code=1i,result_type="read_failed" 1499310362000000000
+net_response,port=31338,protocol=udp,host=localhost,server=localhost,result_text="string_mismatch" result_code=1i,result_type="string_mismatch",string_found=false,response_time=0.00242682 1499310362000000000
+net_response,protocol=udp,host=localhost,server=localhost,port=31338,result_text="success" response_time=0.001128598,result_code=0i,result_type="success",string_found=true 1499310362000000000
+net_response,server=this.domain.does.not.exist,port=443,protocol=udp,host=localhost,result_text="connection_failed" result_code=1i,result_type="connection_failed" 1499310362000000000
 ```
diff --git a/plugins/inputs/net_response/net_response.go b/plugins/inputs/net_response/net_response.go
index 75d0a328..75986843 100644
--- a/plugins/inputs/net_response/net_response.go
+++ b/plugins/inputs/net_response/net_response.go
@@ -13,7 +13,7 @@ import (
 	"github.com/influxdata/telegraf/plugins/inputs"
 )
 
-// NetResponses struct
+// NetResponse struct
 type NetResponse struct {
 	Address     string
 	Timeout     internal.Duration
@@ -23,8 +23,11 @@ type NetResponse struct {
 	Protocol    string
 }
 
-func (_ *NetResponse) Description() string {
-	return "TCP or UDP 'ping' given url and collect response time in seconds"
+var description = "TCP or UDP 'ping' given url and collect response time in seconds"
+
+// Description will return a short string to explain what the plugin does.
+func (*NetResponse) Description() string {
+	return description
 }
 
 var sampleConfig = `
@@ -49,13 +52,17 @@ var sampleConfig = `
   # expect = "ssh"
 `
 
-func (_ *NetResponse) SampleConfig() string {
+// SampleConfig will return a complete configuration example with details about each field.
+func (*NetResponse) SampleConfig() string {
 	return sampleConfig
 }
 
-func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
-	// Prepare fields
-	fields := make(map[string]interface{})
+// TCPGather will execute if there are TCP tests defined in the configuration.
+// It will return a map[string]interface{} for fields and a map[string]string for tags
+func (n *NetResponse) TCPGather() (tags map[string]string, fields map[string]interface{}) {
+	// Prepare returns
+	tags = make(map[string]string)
+	fields = make(map[string]interface{})
 	// Start Timer
 	start := time.Now()
 	// Connecting
@@ -65,11 +72,15 @@ func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
 	// Handle error
 	if err != nil {
 		if e, ok := err.(net.Error); ok && e.Timeout() {
+			tags["result_text"] = "timeout"
+			fields["result_code"] = 1
 			fields["result_type"] = "timeout"
 		} else {
+			tags["result_text"] = "connection_failed"
+			fields["result_code"] = 1
 			fields["result_type"] = "connection_failed"
 		}
-		return fields, nil
+		return tags, fields
 	}
 	defer conn.Close()
 	// Send string if needed
@@ -92,30 +103,42 @@ func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
 		responseTime = time.Since(start).Seconds()
 		// Handle error
 		if err != nil {
+			tags["result_text"] = "read_failed"
+			fields["result_code"] = 1
 			fields["string_found"] = false
-			fields["result_type"] = "read_failed"
+			tags["result_type"] = "read_failed"
+			fields["success"] = 1
 		} else {
 			// Looking for string in answer
 			RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`)
 			find := RegEx.FindString(string(data))
 			if find != "" {
+				tags["result_text"] = "success"
+				fields["result_code"] = 0
 				fields["result_type"] = "success"
 				fields["string_found"] = true
 			} else {
+				tags["result_text"] = "string_mismatch"
+				fields["result_code"] = 1
 				fields["result_type"] = "string_mismatch"
 				fields["string_found"] = false
 			}
 		}
 	} else {
+		tags["result_text"] = "success"
+		fields["result_code"] = 0
 		fields["result_type"] = "success"
 	}
 	fields["response_time"] = responseTime
-	return fields, nil
+	return tags, fields
 }
 
-func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
-	// Prepare fields
-	fields := make(map[string]interface{})
+// UDPGather will execute if there are UDP tests defined in the configuration.
+// It will return a map[string]interface{} for fields and a map[string]string for tags
+func (n *NetResponse) UDPGather() (tags map[string]string, fields map[string]interface{}) {
+	// Prepare returns
+	tags = make(map[string]string)
+	fields = make(map[string]interface{})
 	// Start Timer
 	start := time.Now()
 	// Resolving
@@ -125,8 +148,10 @@ func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
 	conn, err := net.DialUDP("udp", LocalAddr, udpAddr)
 	// Handle error
 	if err != nil {
+		tags["result_text"] = "connection_failed"
+		fields["result_code"] = 1
 		fields["result_type"] = "connection_failed"
-		return fields, nil
+		return tags, fields
 	}
 	defer conn.Close()
 	// Send string
@@ -142,24 +167,35 @@ func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
 	responseTime := time.Since(start).Seconds()
 	// Handle error
 	if err != nil {
+		tags["result_text"] = "read_failed"
+		fields["result_code"] = 1
 		fields["result_type"] = "read_failed"
-		return fields, nil
+		return tags, fields
+	}
+
+	// Looking for string in answer
+	RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`)
+	find := RegEx.FindString(string(buf))
+	if find != "" {
+		tags["result_text"] = "success"
+		fields["result_code"] = 0
+		fields["result_type"] = "success"
+		fields["string_found"] = true
 	} else {
-		// Looking for string in answer
-		RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`)
-		find := RegEx.FindString(string(buf))
-		if find != "" {
-			fields["result_type"] = "success"
-			fields["string_found"] = true
-		} else {
-			fields["result_type"] = "string_mismatch"
-			fields["string_found"] = false
-		}
+		tags["result_text"] = "string_mismatch"
+		fields["result_code"] = 1
+		fields["result_type"] = "string_mismatch"
+		fields["string_found"] = false
 	}
+
 	fields["response_time"] = responseTime
-	return fields, nil
+
+	return tags, fields
 }
 
+// Gather is called by telegraf when the plugin is executed on its interval.
+// It will call either UDPGather or TCPGather based on the configuration and
+// also fill an Accumulator that is supplied.
 func (n *NetResponse) Gather(acc telegraf.Accumulator) error {
 	// Set default values
 	if n.Timeout.Duration == 0 {
@@ -189,18 +225,23 @@ func (n *NetResponse) Gather(acc telegraf.Accumulator) error {
 	// Prepare data
 	tags := map[string]string{"server": host, "port": port}
 	var fields map[string]interface{}
+	var returnTags map[string]string
 	// Gather data
 	if n.Protocol == "tcp" {
-		fields, err = n.TcpGather()
+		returnTags, fields = n.TCPGather()
 		tags["protocol"] = "tcp"
 	} else if n.Protocol == "udp" {
-		fields, err = n.UdpGather()
+		returnTags, fields = n.UDPGather()
 		tags["protocol"] = "udp"
 	} else {
 		return errors.New("Bad protocol")
 	}
-	if err != nil {
-		return err
+	for key, value := range returnTags {
+		tags[key] = value
+	}
+	// Merge the tags
+	for k, v := range returnTags {
+		tags[k] = v
 	}
 	// Add metrics
 	acc.AddFields("net_response", fields, tags)
diff --git a/plugins/inputs/net_response/net_response_test.go b/plugins/inputs/net_response/net_response_test.go
index c7bfb579..0f1ab2f7 100644
--- a/plugins/inputs/net_response/net_response_test.go
+++ b/plugins/inputs/net_response/net_response_test.go
@@ -13,6 +13,21 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestSample(t *testing.T) {
+	c := &NetResponse{}
+	output := c.SampleConfig()
+	if output != sampleConfig {
+		t.Error("Sample config doesn't match")
+	}
+}
+
+func TestDescription(t *testing.T) {
+	c := &NetResponse{}
+	output := c.Description()
+	if output != description {
+		t.Error("Description output is not correct")
+	}
+}
 func TestBadProtocol(t *testing.T) {
 	var acc testutil.Accumulator
 	// Init plugin
@@ -26,6 +41,50 @@ func TestBadProtocol(t *testing.T) {
 	assert.Equal(t, "Bad protocol", err1.Error())
 }
 
+func TestNoPort(t *testing.T) {
+	var acc testutil.Accumulator
+	c := NetResponse{
+		Protocol: "tcp",
+		Address:  ":",
+	}
+	err1 := c.Gather(&acc)
+	require.Error(t, err1)
+	assert.Equal(t, "Bad port", err1.Error())
+}
+
+func TestAddressOnly(t *testing.T) {
+	var acc testutil.Accumulator
+	c := NetResponse{
+		Protocol: "tcp",
+		Address:  "127.0.0.1",
+	}
+	err1 := c.Gather(&acc)
+	require.Error(t, err1)
+	assert.Equal(t, "address 127.0.0.1: missing port in address", err1.Error())
+}
+
+func TestSendExpectStrings(t *testing.T) {
+	var acc testutil.Accumulator
+	tc := NetResponse{
+		Protocol: "udp",
+		Address:  "127.0.0.1:7",
+		Send:     "",
+		Expect:   "toast",
+	}
+	uc := NetResponse{
+		Protocol: "udp",
+		Address:  "127.0.0.1:7",
+		Send:     "toast",
+		Expect:   "",
+	}
+	err1 := tc.Gather(&acc)
+	require.Error(t, err1)
+	assert.Equal(t, "Send string cannot be empty", err1.Error())
+	err2 := uc.Gather(&acc)
+	require.Error(t, err2)
+	assert.Equal(t, "Expected string cannot be empty", err2.Error())
+}
+
 func TestTCPError(t *testing.T) {
 	var acc testutil.Accumulator
 	// Init plugin
@@ -39,12 +98,14 @@ func TestTCPError(t *testing.T) {
 	acc.AssertContainsTaggedFields(t,
 		"net_response",
 		map[string]interface{}{
+			"result_code": 1,
 			"result_type": "connection_failed",
 		},
 		map[string]string{
-			"server":   "",
-			"port":     "9999",
-			"protocol": "tcp",
+			"server":      "",
+			"port":        "9999",
+			"protocol":    "tcp",
+			"result_text": "connection_failed",
 		},
 	)
 }
@@ -77,13 +138,16 @@ func TestTCPOK1(t *testing.T) {
 	acc.AssertContainsTaggedFields(t,
 		"net_response",
 		map[string]interface{}{
+			"result_code":   0,
 			"result_type":   "success",
 			"string_found":  true,
 			"response_time": 1.0,
 		},
-		map[string]string{"server": "127.0.0.1",
-			"port":     "2004",
-			"protocol": "tcp",
+		map[string]string{
+			"result_text": "success",
+			"server":      "127.0.0.1",
+			"port":        "2004",
+			"protocol":    "tcp",
 		},
 	)
 	// Waiting TCPserver
@@ -118,20 +182,23 @@ func TestTCPOK2(t *testing.T) {
 	acc.AssertContainsTaggedFields(t,
 		"net_response",
 		map[string]interface{}{
+			"result_code":   1,
 			"result_type":   "string_mismatch",
 			"string_found":  false,
 			"response_time": 1.0,
 		},
-		map[string]string{"server": "127.0.0.1",
-			"port":     "2004",
-			"protocol": "tcp",
+		map[string]string{
+			"result_text": "string_mismatch",
+			"server":      "127.0.0.1",
+			"port":        "2004",
+			"protocol":    "tcp",
 		},
 	)
 	// Waiting TCPserver
 	wg.Wait()
 }
 
-func TestUDPrror(t *testing.T) {
+func TestUDPError(t *testing.T) {
 	var acc testutil.Accumulator
 	// Init plugin
 	c := NetResponse{
@@ -151,13 +218,15 @@ func TestUDPrror(t *testing.T) {
 	acc.AssertContainsTaggedFields(t,
 		"net_response",
 		map[string]interface{}{
+			"result_code":   1,
 			"result_type":   "read_failed",
 			"response_time": 1.0,
 		},
 		map[string]string{
-			"server":   "",
-			"port":     "9999",
-			"protocol": "udp",
+			"result_text": "read_failed",
+			"server":      "",
+			"port":        "9999",
+			"protocol":    "udp",
 		},
 	)
 }
@@ -190,13 +259,16 @@ func TestUDPOK1(t *testing.T) {
 	acc.AssertContainsTaggedFields(t,
 		"net_response",
 		map[string]interface{}{
+			"result_code":   0,
 			"result_type":   "success",
 			"string_found":  true,
 			"response_time": 1.0,
 		},
-		map[string]string{"server": "127.0.0.1",
-			"port":     "2004",
-			"protocol": "udp",
+		map[string]string{
+			"result_text": "success",
+			"server":      "127.0.0.1",
+			"port":        "2004",
+			"protocol":    "udp",
 		},
 	)
 	// Waiting TCPserver
-- 
GitLab