mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
Merge pull request #321 from areed/host-interfaces
Analyze ipv4 interfaces
This commit is contained in:
19
examples/preflight/host-ipv4-interfaces.yaml
Normal file
19
examples/preflight/host-ipv4-interfaces.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: ipv4Interfaces
|
||||
spec:
|
||||
collectors:
|
||||
- ipv4Interfaces: {}
|
||||
analyzers:
|
||||
- ipv4Interfaces:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count == 0"
|
||||
message: No IPv4 interfaces detected
|
||||
- warn:
|
||||
when: "count >= 2"
|
||||
message: Multiple IPv4 interfaces detected
|
||||
- pass:
|
||||
when: "count == 1"
|
||||
message: IPv4 interface detected
|
||||
@@ -111,6 +111,14 @@ func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getColle
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.IPV4Interfaces != nil {
|
||||
result, err := analyzeHostIPV4Interfaces(hostAnalyzer.IPV4Interfaces, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
}
|
||||
|
||||
|
||||
138
pkg/analyze/host_ipv4interfaces.go
Normal file
138
pkg/analyze/host_ipv4interfaces.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func analyzeHostIPV4Interfaces(hostAnalyzer *troubleshootv1beta2.IPV4InterfacesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents("system/ipv4Interfaces.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var ipv4Interfaces []net.Interface
|
||||
if err := json.Unmarshal(contents, &ipv4Interfaces); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal ipv4Interfaces")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "IPv4 Interfaces"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Fail.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
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
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Warn.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
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
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Pass.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostIPV4InterfacesConditionalToActual(conditional string, ipv4Interfaces []net.Interface) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts in conditional, got %d", len(parts))
|
||||
}
|
||||
|
||||
keyword := parts[0]
|
||||
operator := parts[1]
|
||||
desired := parts[2]
|
||||
|
||||
if keyword != "count" {
|
||||
return false, fmt.Errorf(`Only supported keyword is "count", got %q`, keyword)
|
||||
}
|
||||
|
||||
desiredInt, err := strconv.ParseInt(desired, 10, 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse %q as int", desired)
|
||||
}
|
||||
|
||||
actualCount := len(ipv4Interfaces)
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actualCount < int(desiredInt), nil
|
||||
case "<=":
|
||||
return actualCount <= int(desiredInt), nil
|
||||
case ">":
|
||||
return actualCount > int(desiredInt), nil
|
||||
case ">=":
|
||||
return actualCount >= int(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return actualCount == int(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown operator %q. Supported operators are: <, <=, ==, >=, >", operator)
|
||||
}
|
||||
101
pkg/analyze/host_ipv4interfaces_test.go
Normal file
101
pkg/analyze/host_ipv4interfaces_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeIPV4Interfaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
interfaces []net.Interface
|
||||
hostAnalyzer *troubleshootv1beta2.IPV4InterfacesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "fail when no ipv4 interfaces detected",
|
||||
interfaces: nil,
|
||||
hostAnalyzer: &troubleshootv1beta2.IPV4InterfacesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count > 0",
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count == 0",
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "IPv4 Interfaces",
|
||||
IsFail: true,
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass when ipv4 interfaces detected",
|
||||
interfaces: []net.Interface{
|
||||
{
|
||||
Index: 1,
|
||||
MTU: 1460,
|
||||
HardwareAddr: net.HardwareAddr("42010a80001d"),
|
||||
Name: "ens4",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.IPV4InterfacesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count == 0",
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count > 0",
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "IPv4 Interfaces",
|
||||
IsPass: true,
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.interfaces)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostIPV4Interfaces(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,11 @@ type TCPConnectAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type IPV4InterfacesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostAnalyze struct {
|
||||
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
//
|
||||
@@ -75,4 +80,6 @@ type HostAnalyze struct {
|
||||
BlockDevices *BlockDevicesAnalyze `json:"blockDevices" yaml:"blockDevices"`
|
||||
|
||||
TCPConnect *TCPConnectAnalyze `json:"tcpConnect" yaml:"tcpConnect"`
|
||||
|
||||
IPV4Interfaces *IPV4InterfacesAnalyze `json:"ipv4Interfaces" yaml:"ipv4Interfaces"`
|
||||
}
|
||||
|
||||
@@ -1177,6 +1177,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(TCPConnectAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IPV4Interfaces != nil {
|
||||
in, out := &in.IPV4Interfaces, &out.IPV4Interfaces
|
||||
*out = new(IPV4InterfacesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
|
||||
@@ -1470,6 +1475,33 @@ func (in *IPV4Interfaces) DeepCopy() *IPV4Interfaces {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IPV4InterfacesAnalyze) DeepCopyInto(out *IPV4InterfacesAnalyze) {
|
||||
*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 IPV4InterfacesAnalyze.
|
||||
func (in *IPV4InterfacesAnalyze) DeepCopy() *IPV4InterfacesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IPV4InterfacesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImagePullSecret) DeepCopyInto(out *ImagePullSecret) {
|
||||
*out = *in
|
||||
|
||||
@@ -38,6 +38,8 @@ func (c *HostCollector) RunCollectorSync() (result map[string][]byte, err error)
|
||||
result, err = HostBlockDevices(c)
|
||||
} else if c.Collect.TCPConnect != nil {
|
||||
result, err = HostTCPConnect(c)
|
||||
} else if c.Collect.IPV4Interfaces != nil {
|
||||
result, err = HostIPV4Interfaces(c)
|
||||
} else {
|
||||
err = errors.New("no spec found to run")
|
||||
return
|
||||
|
||||
40
pkg/collect/host_ipv4interfaces.go
Normal file
40
pkg/collect/host_ipv4interfaces.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func HostIPV4Interfaces(c *HostCollector) (map[string][]byte, error) {
|
||||
var ipv4Interfaces []net.Interface
|
||||
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "list host network interfaces")
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
ip, _ := getIPv4FromInterface(&iface)
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
ipv4Interfaces = append(ipv4Interfaces, iface)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal network interfaces")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
"system/ipv4Interfaces.json": b,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user