Skip to content

Commit

Permalink
Analyze ipv4 interfaces
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Lavery <laverya@umich.edu>
  • Loading branch information
Andrew Reed and laverya committed Feb 15, 2021
1 parent b0a0057 commit 6498c34
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 0 deletions.
19 changes: 19 additions & 0 deletions examples/preflight/host-ipv4-interfaces.yaml
@@ -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
8 changes: 8 additions & 0 deletions pkg/analyze/analyzer.go
Expand Up @@ -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")
}

Expand Down
138 changes: 138 additions & 0 deletions pkg/analyze/host_ipv4interfaces.go
@@ -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 changes: 101 additions & 0 deletions pkg/analyze/host_ipv4interfaces_test.go
@@ -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)
})
}
}
7 changes: 7 additions & 0 deletions pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go
Expand Up @@ -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"`
//
Expand All @@ -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"`
}
32 changes: 32 additions & 0 deletions pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/collect/host_collector.go
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions pkg/collect/host_ipv4interfaces.go
@@ -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
}

0 comments on commit 6498c34

Please sign in to comment.