Merge pull request #317 from areed/host-remote-port

Analyze TCP connection
This commit is contained in:
Andrew Reed
2021-02-15 15:18:11 -05:00
committed by GitHub
9 changed files with 336 additions and 1 deletions
+25
View File
@@ -0,0 +1,25 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: connect
spec:
collectors:
- tcpConnect:
collectorName: weave host 1
address: 10.128.0.2:6783
analyzers:
- tcpConnect:
collectorName: weave host 1
outcomes:
- fail:
when: "connection-refused"
message: Connection to weave on host 1 was refused
- fail:
when: "connection-timeout"
message: Timed out connecting to weave on host 1
- fail:
when: "error"
message: Unexpected error connecting to weave on host 1
- pass:
when: "connected"
message: Successfully connected to weave on host 1
+7 -1
View File
@@ -104,7 +104,13 @@ func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getColle
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.TCPConnect != nil {
result, err := analyzeHostTCPConnect(hostAnalyzer.TCPConnect, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
}
+86
View File
@@ -0,0 +1,86 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostTCPConnect(hostAnalyzer *troubleshootv1beta2.TCPConnectAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fullPath := path.Join("connect", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
collected, err := getCollectedFileContents(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
}
actual := collect.NetworkStatusResult{}
if err := json.Unmarshal(collected, &actual); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal collected")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "TCP Connection Attempt"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
if string(actual.Status) == outcome.Fail.When {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
if string(actual.Status) == outcome.Warn.When {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
if string(actual.Status) == outcome.Pass.When {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
+92
View File
@@ -0,0 +1,92 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeTCPConnect(t *testing.T) {
tests := []struct {
name string
info *collect.NetworkStatusResult
hostAnalyzer *troubleshootv1beta2.TCPConnectAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "connection refused, fail",
info: &collect.NetworkStatusResult{
Status: collect.NetworkStatusConnectionRefused,
},
hostAnalyzer: &troubleshootv1beta2.TCPConnectAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "connection-refused",
Message: "Connection was refused",
},
},
},
},
result: &AnalyzeResult{
Title: "TCP Connection Attempt",
IsFail: true,
Message: "Connection was refused",
},
},
{
name: "connected, fail",
info: &collect.NetworkStatusResult{
Status: collect.NetworkStatusConnected,
},
hostAnalyzer: &troubleshootv1beta2.TCPConnectAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "connection-refused",
Message: "Connection was refused",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "connected",
Message: "Connection was successful",
},
},
},
},
result: &AnalyzeResult{
Title: "TCP Connection Attempt",
IsPass: true,
Message: "Connection was successful",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.info)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostTCPConnect(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
@@ -50,6 +50,12 @@ type BlockDevicesAnalyze struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type TCPConnectAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type HostAnalyze struct {
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
//
@@ -67,4 +73,6 @@ type HostAnalyze struct {
Time *TimeAnalyze `json:"time" yaml:"time"`
BlockDevices *BlockDevicesAnalyze `json:"blockDevices" yaml:"blockDevices"`
TCPConnect *TCPConnectAnalyze `json:"tcpConnect" yaml:"tcpConnect"`
}
@@ -67,6 +67,12 @@ type HostBlockDevices struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
type TCPConnect struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Address string `json:"address"`
Timeout string `json:"timeout,omitempty"`
}
type HostCollect struct {
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
@@ -79,6 +85,7 @@ type HostCollect struct {
HTTP *HostHTTP `json:"http,omitempty" yaml:"http,omitempty"`
Time *HostTime `json:"time,omitempty" yaml:"time,omitempty"`
BlockDevices *HostBlockDevices `json:"blockDevices,omitempty" yaml:"blockDevices,omitempty"`
TCPConnect *TCPConnect `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
}
func (c *HostCollect) GetName() string {
@@ -1172,6 +1172,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
*out = new(BlockDevicesAnalyze)
(*in).DeepCopyInto(*out)
}
if in.TCPConnect != nil {
in, out := &in.TCPConnect, &out.TCPConnect
*out = new(TCPConnectAnalyze)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
@@ -1258,6 +1263,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
*out = new(HostBlockDevices)
**out = **in
}
if in.TCPConnect != nil {
in, out := &in.TCPConnect, &out.TCPConnect
*out = new(TCPConnect)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
@@ -2347,6 +2357,49 @@ func (in *SupportBundleVersionSpec) DeepCopy() *SupportBundleVersionSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPConnect) DeepCopyInto(out *TCPConnect) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPConnect.
func (in *TCPConnect) DeepCopy() *TCPConnect {
if in == nil {
return nil
}
out := new(TCPConnect)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPConnectAnalyze) DeepCopyInto(out *TCPConnectAnalyze) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPConnectAnalyze.
func (in *TCPConnectAnalyze) DeepCopy() *TCPConnectAnalyze {
if in == nil {
return nil
}
out := new(TCPConnectAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPLoadBalancer) DeepCopyInto(out *TCPLoadBalancer) {
*out = *in
+2
View File
@@ -36,6 +36,8 @@ func (c *HostCollector) RunCollectorSync() (result map[string][]byte, err error)
result, err = HostTime(c)
} else if c.Collect.BlockDevices != nil {
result, err = HostBlockDevices(c)
} else if c.Collect.TCPConnect != nil {
result, err = HostTCPConnect(c)
} else {
err = errors.New("no spec found to run")
return
+56
View File
@@ -0,0 +1,56 @@
package collect
import (
"encoding/json"
"fmt"
"net"
"path"
"strings"
"time"
"github.com/pkg/errors"
)
func HostTCPConnect(c *HostCollector) (map[string][]byte, error) {
address := c.Collect.TCPConnect.Address
timeout := 10 * time.Second
if c.Collect.TCPConnect.Timeout != "" {
var err error
timeout, err = time.ParseDuration(c.Collect.TCPConnect.Timeout)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse timeout %q", c.Collect.TCPConnect.Timeout)
}
}
result := NetworkStatusResult{
Status: attemptConnect(address, timeout),
}
b, err := json.Marshal(result)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal result")
}
name := path.Join("connect", fmt.Sprintf("%s.json", c.Collect.TCPConnect.CollectorName))
return map[string][]byte{
name: b,
}, nil
}
func attemptConnect(address string, timeout time.Duration) NetworkStatus {
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
if strings.Contains(err.Error(), "i/o timeout") {
return NetworkStatusConnectionTimeout
}
if strings.Contains(err.Error(), "connection refused") {
return NetworkStatusConnectionRefused
}
return NetworkStatusErrorOther
}
conn.Close()
return NetworkStatusConnected
}