Host preflight (#311)

* Add HostPreflight v1beta2

* Work on TCP Load Balancer

* Host disk usage collector and analyzer

* Host memory analyzer

* TCP port status

* TCP load balancer

* Review changes

Co-authored-by: Marc Campbell <marc.e.campbell@gmail.com>
This commit is contained in:
Andrew Reed
2021-02-08 16:09:01 -05:00
committed by GitHub
co-authored by Marc Campbell
parent df68f7445f
commit 10a34c2e58
38 changed files with 3284 additions and 59 deletions
+82 -36
View File
@@ -86,15 +86,16 @@ func runPreflights(v *viper.Viper, arg string) error {
return errors.Wrapf(err, "failed to parse %s", arg)
}
preflightSpec := obj.(*troubleshootv1beta2.Preflight)
var collectResults preflight.CollectResult
preflightSpecName := ""
finishedCh := make(chan bool, 1)
progressCh := make(chan interface{}, 0) // non-zero buffer will result in missed messages
s := spin.New()
finishedCh := make(chan bool, 1)
progressChan := make(chan interface{}, 0) // non-zero buffer will result in missed messages
go func() {
for {
select {
case msg, ok := <-progressChan:
case msg, ok := <-progressCh:
if !ok {
continue
}
@@ -114,47 +115,40 @@ func runPreflights(v *viper.Viper, arg string) error {
}
}
}()
defer func() {
close(finishedCh)
close(progressCh)
}()
restConfig, err := k8sutil.GetRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
collectOpts := preflight.CollectOpts{
Namespace: v.GetString("namespace"),
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
ProgressChan: progressChan,
KubernetesRestConfig: restConfig,
}
if v.GetString("since") != "" || v.GetString("since-time") != "" {
err := parseTimeFlags(v, progressChan, preflightSpec.Spec.Collectors)
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
r, err := collectInCluster(preflightSpec, finishedCh, progressCh)
if err != nil {
return err
return errors.Wrap(err, "failed to collect in cluster")
}
collectResults = *r
preflightSpecName = preflightSpec.Name
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
r, err := collectHost(hostPreflightSpec, finishedCh, progressCh)
if err != nil {
return errors.Wrap(err, "failed to collect from host")
}
collectResults = *r
preflightSpecName = hostPreflightSpec.Name
}
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
if err != nil {
if !collectResults.IsRBACAllowed {
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, collectResults.Collectors)
if err != nil {
progressChan <- err
}
}
}
return err
if collectResults == nil {
return errors.New("no results")
}
analyzeResults := collectResults.Analyze()
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
if err != nil {
progressChan <- err
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
if err != nil {
progressCh <- err
}
}
}
@@ -164,10 +158,62 @@ func runPreflights(v *viper.Viper, arg string) error {
if len(analyzeResults) == 0 {
return errors.New("no data has been collected")
}
return showInteractiveResults(preflightSpec.Name, analyzeResults)
return showInteractiveResults(preflightSpecName, analyzeResults)
}
return showStdoutResults(v.GetString("format"), preflightSpec.Name, analyzeResults)
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
}
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
v := viper.GetViper()
restConfig, err := k8sutil.GetRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
collectOpts := preflight.CollectOpts{
Namespace: v.GetString("namespace"),
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
ProgressChan: progressCh,
KubernetesRestConfig: restConfig,
}
if v.GetString("since") != "" || v.GetString("since-time") != "" {
err := parseTimeFlags(v, progressCh, preflightSpec.Spec.Collectors)
if err != nil {
return nil, err
}
}
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
if err != nil {
if !collectResults.IsRBACAllowed() {
if preflightSpec.Spec.UploadResultsTo != "" {
clusterCollectResults := collectResults.(preflight.ClusterCollectResult)
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, clusterCollectResults.Collectors)
if err != nil {
progressCh <- err
}
}
}
return nil, err
}
return &collectResults, nil
}
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
collectOpts := preflight.CollectOpts{
ProgressChan: progressCh,
}
collectResults, err := preflight.CollectHost(collectOpts, hostPreflightSpec)
if err != nil {
return nil, errors.Wrap(err, "failed to collect from host")
}
return &collectResults, nil
}
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors []*troubleshootv1beta2.Collect) error {
+22
View File
@@ -0,0 +1,22 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: cpu
spec:
collectors:
- cpu: {}
analyzers:
- cpu:
outcomes:
- fail:
when: "physical < 4"
message: At least 4 physical CPU cores are required
- fail:
when: "logical < 8"
message: At least 8 CPU cores are required
- warn:
when: "count < 16"
message: At least 16 CPU cores preferred
- pass:
message: This server has sufficient CPU cores
+33
View File
@@ -0,0 +1,33 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: diskUsage
spec:
collectors:
- diskUsage:
collectorName: ephemeral
path: /var/lib/kubelet
analyzers:
- diskUsage:
collectorName: ephemeral
outcomes:
- fail:
when: "total < 20Gi"
message: /var/lib/kubelet has less than 20Gi of total space
- fail:
when: "available < 10Gi"
message: /var/lib/kubelet has less than 10Gi of disk space available
- fail:
when: "used/total > 70%"
message: /var/lib/kubelet is more than 70% full
- warn:
when: "total < 40Gi"
message: /var/lib/kubelet has less than 40Gi of total space
- warn:
when: "used/total > 60%"
message: /var/lib/kubelet is more than 60% full
- pass:
when: "available/total >= 90%"
message: /var/lib/kubelet has more than 90% available
- pass:
message: /var/lib/kubelet has sufficient disk space available
+19
View File
@@ -0,0 +1,19 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: memory
spec:
collectors:
- memory:
collectorName: memory
analyzers:
- memory:
outcomes:
- fail:
when: "< 8Gi"
message: At least 8Gi of memory is required
- warn:
when: "< 32Gi"
message: At least 32Gi of memory is recommended
- pass:
message: The system has as sufficient memory
+30
View File
@@ -0,0 +1,30 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: port
spec:
collectors:
- tcpPortStatus:
collectorName: k8s
port: 7443
analyzers:
- tcpPortStatus:
collectorName: k8s
outcomes:
- fail:
when: "connection-refused"
message: Connection to port 7443 was refused.
- fail:
when: "address-in-use"
message: Another process was already listening on port 7443.
- fail:
when: "connection-timeout"
message: Timed out connecting to port 7443. Check your firewall.
- fail:
when: "error"
message: Unexpected port status
- pass:
when: "connected"
message: Port 7443 is open
- warn:
message: Unexpected port status
@@ -0,0 +1,31 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: loadbalancer
spec:
collectors:
- tcpLoadBalancer:
collectorName: loadbalancer
port: 7443
address: 10.128.0.29:7444
analyzers:
- tcpLoadBalancer:
collectorName: loadbalancer
outcomes:
- fail:
when: "connection-refused"
message: Connection to port 7443 via load balancer was refused.
- fail:
when: "address-in-use"
message: Another process was already listening on port 7443.
- fail:
when: "connection-timeout"
message: Timed out connecting to port 7443 via load balancer. Check your firewall.
- fail:
when: "error"
message: Unexpected port status
- pass:
when: "connected"
message: Successfully connected to port 7443 via load balancer
- warn:
message: Unexpected port status
@@ -0,0 +1,51 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: example
spec:
collectors:
- tcpLoadBalancer:
collectorName: LB1
address: 10.1.1.1
port: 6443
timeout: 5000ms
- diskUsage:
collectorName: ephemeral
path: /var/lib/kubelet
analyzers:
- cpu:
outcomes:
- fail:
when: "< 4"
message: This server has less than 4 CPU cores, and we require 8, but recommend 16
- warn:
when: "< 16"
message: This server has at least 4 CPU cores, but we recommend 16 or more
- pass:
message: This server has sufficient CPU cores
- tcpLoadBalancer:
collectorName: LB1
outcomes:
- fail:
when: "connection-timeout"
message: The TCP Load Balancer is not forwarding traffic to this server.
- fail:
when: "address-in-use"
message: The local port is not available to validate the Load Balancer configuration.
- pass:
when: "connected"
message: The specified TCP Load Balancer appears to be properly forwarding traffic to this server.
- diskUsage:
collectorName: ephemeral
outcomes:
- fail:
when: "total < 20Gi"
message: /var/lib/kubelet has less than 20Gi of total space
- fail:
when: "available < 10Gi"
message: /var/lib/kubelet has less than 10Gi of disk space available
- fail:
when: "used/total > 70%"
message: /var/lib/kubelet is more than 70% full
- pass:
message: /var/lib/kubelet has sufficient disk space available
+1
View File
@@ -32,6 +32,7 @@ require (
github.com/prometheus/procfs v0.0.5 // indirect
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
github.com/segmentio/ksuid v1.0.3
github.com/shirou/gopsutil v3.20.12+incompatible
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.4.0
+2
View File
@@ -414,6 +414,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY=
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.20.12+incompatible h1:6VEGkOXP/eP4o2Ilk8cSsX0PhOEfX6leqAnD+urrp9M=
github.com/shirou/gopsutil v3.20.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+41 -1
View File
@@ -40,6 +40,46 @@ func isExcluded(excludeVal multitype.BoolOrString) (bool, error) {
return parsed, nil
}
func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
if hostAnalyzer.CPU != nil {
result, err := analyzeHostCPU(hostAnalyzer.CPU, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.TCPLoadBalancer != nil {
result, err := analyzeHostTCPLoadBalancer(hostAnalyzer.TCPLoadBalancer, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.DiskUsage != nil {
result, err := analyzeHostDiskUsage(hostAnalyzer.DiskUsage, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.Memory != nil {
result, err := analyzeHostMemory(hostAnalyzer.Memory, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.TCPPortStatus != nil {
result, err := analyzeHostTCPPortStatus(hostAnalyzer.TCPPortStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
}
func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
if analyzer.ClusterVersion != nil {
isExcluded, err := isExcluded(analyzer.ClusterVersion.Exclude)
@@ -265,6 +305,6 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
return nil, errors.New("invalid analyzer")
}
+165
View File
@@ -0,0 +1,165 @@
package analyzer
import (
"encoding/json"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostCPU(hostAnalyzer *troubleshootv1beta2.CPUAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/cpu.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
cpuInfo := collect.CPUInfo{}
if err := json.Unmarshal(contents, &cpuInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal cpu info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Number of CPUs"
}
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 := compareHostCPUConditionalToActual(outcome.Fail.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
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 := compareHostCPUConditionalToActual(outcome.Warn.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
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 := compareHostCPUConditionalToActual(outcome.Pass.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostCPUConditionalToActual(conditional string, logicalCount int, physicalCount int) (res bool, err error) {
compareLogical := false
comparePhysical := false
compareUnspecified := false
comparator := ""
desired := ""
parts := strings.Split(conditional, " ")
if len(parts) == 3 {
comparator = parts[1]
desired = parts[2]
if strings.ToLower(parts[0]) == "logical" {
compareLogical = true
} else if strings.ToLower(parts[0]) == "physical" {
comparePhysical = true
} else if strings.ToLower(parts[0]) == "count" {
compareUnspecified = true
}
} else if len(parts) == 2 {
compareUnspecified = true
comparator = parts[0]
desired = parts[1]
}
if !compareLogical && !comparePhysical && !compareUnspecified {
return false, errors.New("unable to parse conditional")
}
if compareLogical {
return doCompareHostCPU(comparator, desired, logicalCount)
} else if comparePhysical {
return doCompareHostCPU(comparator, desired, physicalCount)
} else {
actual := logicalCount
if physicalCount > logicalCount {
actual = physicalCount
}
return doCompareHostCPU(comparator, desired, actual)
}
}
func doCompareHostCPU(operator string, desired string, actual int) (bool, error) {
desiredInt, err := strconv.ParseInt(desired, 10, 64)
if err != nil {
return false, errors.Wrap(err, "failed to parse")
}
switch operator {
case "<":
return actual < int(desiredInt), nil
case "<=":
return actual <= int(desiredInt), nil
case ">":
return actual > int(desiredInt), nil
case ">=":
return actual >= int(desiredInt), nil
case "=", "==", "===":
return actual == int(desiredInt), nil
}
return false, errors.New("unknown operator")
}
+150
View File
@@ -0,0 +1,150 @@
package analyzer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_doCompareHostCPU(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual int
expected bool
}{
{
name: "< 16",
operator: "<",
desired: "16",
actual: 8,
expected: true,
},
{
name: "< 8 when actual is 8",
operator: "<",
desired: "8",
actual: 8,
expected: false,
},
{
name: "<= 8 when actual is 8",
operator: "<=",
desired: "8",
actual: 8,
expected: true,
},
{
name: "<= 8 when actual is 16",
operator: "<=",
desired: "8",
actual: 16,
expected: false,
},
{
name: "== 8 when actual is 16",
operator: "==",
desired: "8",
actual: 16,
expected: false,
},
{
name: "== 8 when actual is 8",
operator: "==",
desired: "8",
actual: 8,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostCPU(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_compareHostCPUConditionalToActual(t *testing.T) {
tests := []struct {
name string
when string
logicalCount int
physicalCount int
expected bool
}{
{
name: "physical > 4, when physical is 8",
when: "physical > 4",
logicalCount: 0,
physicalCount: 8,
expected: true,
},
{
name: "physical > 4, when physical is 4",
when: "physical > 4",
logicalCount: 0,
physicalCount: 4,
expected: false,
},
{
name: "physical > 4, when physical is 3, logical is 6",
when: "physical > 4",
logicalCount: 6,
physicalCount: 3,
expected: false,
},
{
name: "logical > 4, when physical is 4, logical is 8",
when: "logical > 4",
logicalCount: 8,
physicalCount: 4,
expected: true,
},
{
name: ">= 4, when physical is 2, logical is 4",
when: ">= 4",
logicalCount: 4,
physicalCount: 2,
expected: true,
},
{
name: "count < 4, when physical is 2, logical is 4",
when: "count < 4",
logicalCount: 4,
physicalCount: 2,
expected: false,
},
{
name: "count <= 4, when physical is 2, logical is 4",
when: "count <= 4",
logicalCount: 4,
physicalCount: 2,
expected: true,
},
{
name: "== 4, physical is 4, logical is 4",
when: "== 4",
logicalCount: 4,
physicalCount: 4,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareHostCPUConditionalToActual(test.when, test.logicalCount, test.physicalCount)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
+185
View File
@@ -0,0 +1,185 @@
package analyzer
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeHostDiskUsage(hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
key := collect.HostDiskUsageKey(hostAnalyzer.CollectorName)
contents, err := getCollectedFileContents(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to get collected file %s", key)
}
diskUsageInfo := collect.DiskUsageInfo{}
if err := json.Unmarshal(contents, &diskUsageInfo); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal disk usage info from %s", key)
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = fmt.Sprintf("Disk Usage %s", hostAnalyzer.CollectorName)
}
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 := compareHostDiskUsageConditionalToActual(outcome.Fail.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", 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 := compareHostDiskUsageConditionalToActual(outcome.Warn.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", 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 := compareHostDiskUsageConditionalToActual(outcome.Pass.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", 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 compareHostDiskUsageConditionalToActual(conditional string, totalBytes uint64, usedBytes uint64) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 3 {
return false, fmt.Errorf("conditional must have exactly 3 parts, got %d", len(parts))
}
stat := strings.ToLower(parts[0])
comparator := parts[1]
desired := parts[2]
switch stat {
case "total":
return doCompareHostDiskUsage(comparator, desired, totalBytes)
case "used":
return doCompareHostDiskUsage(comparator, desired, usedBytes)
case "available":
return doCompareHostDiskUsage(comparator, desired, totalBytes-usedBytes)
case "used/total":
used := float64(usedBytes) / float64(totalBytes)
return doCompareHostDiskUsagePercent(comparator, desired, used)
case "available/total":
available := float64(totalBytes-usedBytes) / float64(totalBytes)
return doCompareHostDiskUsagePercent(comparator, desired, available)
}
return false, fmt.Errorf("unknown disk usage statistic %q", stat)
}
func doCompareHostDiskUsage(operator string, desired string, actual uint64) (bool, error) {
quantity, err := resource.ParseQuantity(desired)
if err != nil {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
desiredInt, ok := quantity.AsInt64()
if !ok {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
switch operator {
case "<":
return actual < uint64(desiredInt), nil
case "<=":
return actual <= uint64(desiredInt), nil
case ">":
return actual > uint64(desiredInt), nil
case ">=":
return actual >= uint64(desiredInt), nil
case "=", "==", "===":
return actual == uint64(desiredInt), nil
}
return false, errors.New("unknown operator")
}
func doCompareHostDiskUsagePercent(operator string, desired string, actual float64) (bool, error) {
isPercent := false
if strings.HasSuffix(desired, "%") {
desired = strings.TrimSuffix(desired, "%")
isPercent = true
}
desiredPercent, err := strconv.ParseFloat(desired, 64)
if err != nil {
return false, errors.Wrap(err, "parsed desired quantity")
}
if isPercent {
desiredPercent = desiredPercent / 100.0
}
switch operator {
case "<":
return actual < desiredPercent, nil
case "<=":
return actual <= desiredPercent, nil
case ">":
return actual > desiredPercent, nil
case ">=":
return actual >= desiredPercent, nil
case "=", "==", "===":
return actual == desiredPercent, nil
}
return false, errors.New("unknown operator")
}
+380
View File
@@ -0,0 +1,380 @@
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 Test_doCompareHostDiskUsage(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual uint64
expected bool
}{
{
name: ">= 1Gi, when actual is 2Gi",
operator: ">=",
desired: "1Gi",
actual: 2147483648,
expected: true,
},
{
name: "<= 1Gi, when actual is 1GB",
operator: "<=",
desired: "1Gi",
actual: 1000000000,
expected: true,
},
{
name: "< 20Gi, when actual is 15Gi",
operator: "<",
desired: "20Gi",
actual: 15 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "< 20Gi, when actual is 20Gi",
operator: "<",
desired: "20Gi",
actual: 20 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "> 1073741824, when actual is 1024",
operator: ">",
desired: "1073741824",
actual: 1024,
expected: false,
},
{
name: "= 4096, when actual is 4096",
operator: "=",
desired: "4096",
actual: 4096,
expected: true,
},
{
name: "= 4096, when actual is 1024",
operator: "=",
desired: "4096",
actual: 1024,
expected: false,
},
{
name: "= 4096, when actual is 5000",
operator: "=",
desired: "4096",
actual: 5000,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostDiskUsage(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_doCompareHostDiskUsagePercent(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual float64
expected bool
}{
{
name: ">= .20, when actual is .30",
operator: ">=",
desired: ".20",
actual: .30,
expected: true,
},
{
name: ">= .20, when actual is .10",
operator: ">=",
desired: ".20",
actual: .10,
expected: false,
},
{
name: ">= .20, when actual is .20",
operator: ">=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "> .20, when actual is .30",
operator: ">",
desired: ".20",
actual: .30,
expected: true,
},
{
name: "> .20, when actual is .10",
operator: ">",
desired: ".20",
actual: .10,
expected: false,
},
{
name: "> .20, when actual is .20",
operator: ">",
desired: ".20",
actual: .20,
expected: false,
},
{
name: "<= .20, when actual is .30",
operator: "<=",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "<= .20, when actual is .10",
operator: "<=",
desired: ".20",
actual: .10,
expected: true,
},
{
name: "<= .20, when actual is .20",
operator: "<=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "< .20, when actual is .30",
operator: "<",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "< .20, when actual is .10",
operator: "<",
desired: ".20",
actual: .10,
expected: true,
},
{
name: "< .20, when actual is .20",
operator: "<",
desired: ".20",
actual: .20,
expected: false,
},
{
name: "= .20, when actual is .30",
operator: "=",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "= .20, when actual is .10",
operator: "=",
desired: ".20",
actual: .10,
expected: false,
},
{
name: "= .20, when actual is .20",
operator: "=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "= 20%, when actual is .20",
operator: "=",
desired: "20%",
actual: .20,
expected: true,
},
{
name: "= 20%, when actual is 20",
operator: "=",
desired: "20%",
actual: 20,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostDiskUsagePercent(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func TestAnalyzeHostDiskUsage(t *testing.T) {
tests := []struct {
name string
diskUsageInfo *collect.DiskUsageInfo
hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "Fail on insuffient total ephemeral disk space",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 10 * 1024 * 1024 * 1024,
UsedBytes: 5 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total <= 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsFail: true,
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
name: "Fail on insuffient available ephemeral disk space percentage",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 10 * 1024 * 1024 * 1024,
UsedBytes: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total < 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsFail: true,
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
name: "Warn on high ephemeral disk space usage",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 1024 * 1024 * 1024 * 1024,
UsedBytes: 100 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total < 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "used >= 100Gi",
Message: "/var/lib/kubelet has more than 100Gi used",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsWarn: true,
Message: "/var/lib/kubelet has more than 100Gi used",
},
},
{
name: "Pass on ephemeral disk space available",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 12 * 1024 * 1024 * 1024,
UsedBytes: 1 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "available > 10Gi",
Message: "/var/lib/kubelet has at least 10Gi available",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsPass: true,
Message: "/var/lib/kubelet has at least 10Gi available",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.diskUsageInfo)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostDiskUsage(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+134
View File
@@ -0,0 +1,134 @@
package analyzer
import (
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeHostMemory(hostAnalyzer *troubleshootv1beta2.MemoryAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/memory.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
memoryInfo := collect.MemoryInfo{}
if err := json.Unmarshal(contents, &memoryInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal memory info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Amount of Memory"
}
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 := compareHostMemoryConditionalToActual(outcome.Fail.When, memoryInfo.Total)
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 := compareHostMemoryConditionalToActual(outcome.Warn.When, memoryInfo.Total)
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 := compareHostMemoryConditionalToActual(outcome.Pass.When, memoryInfo.Total)
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 compareHostMemoryConditionalToActual(conditional string, total uint64) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 2 {
return false, fmt.Errorf("Expected 2 parts in conditional, got %d", len(parts))
}
operator := parts[0]
desired := parts[1]
quantity, err := resource.ParseQuantity(desired)
if err != nil {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
desiredInt, ok := quantity.AsInt64()
if !ok {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
switch operator {
case "<":
return total < uint64(desiredInt), nil
case "<=":
return total <= uint64(desiredInt), nil
case ">":
return total > uint64(desiredInt), nil
case ">=":
return total >= uint64(desiredInt), nil
case "=", "==", "===":
return total == uint64(desiredInt), nil
}
return false, errors.New("unknown operator")
}
+177
View File
@@ -0,0 +1,177 @@
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 Test_doCompareHostMemory(t *testing.T) {
tests := []struct {
name string
conditional string
actual uint64
expected bool
}{
{
name: "< 16Gi when actual is 8Gi",
conditional: "< 16Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "< 8Gi when actual is 8Gi",
conditional: "< 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "<= 8Gi when actual is 8Gi",
conditional: "<= 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "<= 8Gi when actual is 16Gi",
conditional: "<= 8Gi",
actual: 16 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "== 8Gi when actual is 16Gi",
conditional: "== 8Gi",
actual: 16 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "== 8Gi when actual is 8Gi",
conditional: "== 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "== 8000000000 when actual is 8000000000",
conditional: "== 8000000000",
actual: 8 * 1000 * 1000 * 1000,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareHostMemoryConditionalToActual(test.conditional, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func TestAnalyzeHostMemory(t *testing.T) {
tests := []struct {
name string
memoryInfo *collect.MemoryInfo
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "Pass on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ">= 4Gi",
Message: "System has at least 4Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsPass: true,
Message: "System has at least 4Gi of memory",
},
},
{
name: "Fail on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 16Gi",
Message: "System requires at least 16Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsFail: true,
Message: "System requires at least 16Gi of memory",
},
},
{
name: "Warn on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 4Gi",
Message: "System requires at least 4Gi of memory",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "<= 8Gi",
Message: "System performs best with more than 8Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsWarn: true,
Message: "System performs best with more than 8Gi of memory",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.memoryInfo)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostMemory(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+91
View File
@@ -0,0 +1,91 @@
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 analyzeHostTCPLoadBalancer(hostAnalyzer *troubleshootv1beta2.TCPLoadBalancerAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := hostAnalyzer.CollectorName
if collectorName == "" {
collectorName = "tcpLoadBalancer"
}
fullPath := path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", 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 Load Balancer"
}
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
}
+89
View File
@@ -0,0 +1,89 @@
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 analyzeHostTCPPortStatus(hostAnalyzer *troubleshootv1beta2.TCPPortStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fullPath := path.Join("tcpPortStatus", "tcpPortStatus.json")
if hostAnalyzer.CollectorName != "" {
fullPath = path.Join("tcpPortStatus", 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 Port Status"
}
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
}
@@ -4,18 +4,6 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/multitype"
)
type SingleOutcome struct {
When string `json:"when,omitempty" yaml:"when,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
URI string `json:"uri,omitempty" yaml:"uri,omitempty"`
}
type Outcome struct {
Fail *SingleOutcome `json:"fail,omitempty" yaml:"fail,omitempty"`
Warn *SingleOutcome `json:"warn,omitempty" yaml:"warn,omitempty"`
Pass *SingleOutcome `json:"pass,omitempty" yaml:"pass,omitempty"`
}
type ClusterVersion struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
@@ -0,0 +1,41 @@
package v1beta2
type CPUAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type MemoryAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type TCPLoadBalancerAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type TCPPortStatusAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type DiskUsageAnalyze 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"`
//
TCPLoadBalancer *TCPLoadBalancerAnalyze `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
DiskUsage *DiskUsageAnalyze `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
Memory *MemoryAnalyze `json:"memory,omitempty" yaml:"memory,omitempty"`
TCPPortStatus *TCPPortStatusAnalyze `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
}
@@ -0,0 +1,80 @@
package v1beta2
import (
"github.com/replicatedhq/troubleshoot/pkg/multitype"
)
type HostCollectorMeta struct {
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
// +optional
Exclude multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}
type CPU struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
type Memory struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
type TCPLoadBalancer struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Address string `json:"address"`
Port int `json:"port"`
Timeout string `json:"timeout,omitempty"`
}
type HTTPLoadBalancer struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Address string `json:"address"`
Port int `json:"port"`
Path string `json:"path"`
Timeout string `json:"timeout,omitempty"`
}
type TCPPortStatus struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Interface string `json:"interface,omitempty"`
Port int `json:"port"`
}
type Kubernetes struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
type IPV4Interfaces struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
type DiskUsage struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Path string `json:"path"`
}
type HostCollect struct {
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
TCPLoadBalancer *TCPLoadBalancer `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
HTTPLoadBalancer *HTTPLoadBalancer `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,omitempty"`
TCPPortStatus *TCPPortStatus `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
Kubernetes *Kubernetes `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
IPV4Interfaces *IPV4Interfaces `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
DiskUsage *DiskUsage `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
}
func (c *HostCollect) GetName() string {
var collector string
if c.CPU != nil {
collector = "cpu"
}
if c.Memory != nil {
collector = "memory"
}
if collector == "" {
return "<none>"
}
return collector
}
@@ -0,0 +1,57 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HostPreflightSpec defines the desired state of HostPreflight
type HostPreflightSpec struct {
Collectors []*HostCollect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
Analyzers []*HostAnalyze `json:"analyzers,omitempty" yaml:"analyzers,omitempty"`
}
// HostPreflightStatus defines the observed state of HostPreflight
type HostPreflightStatus struct {
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// HostPreflight is the Schema for the hostpreflights API
// +k8s:openapi-gen=true
type HostPreflight struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec HostPreflightSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
Status HostPreflightStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// HostPreflightList contains a list of HostPreflight
type HostPreflightList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []HostPreflight `json:"items"`
}
func init() {
SchemeBuilder.Register(&HostPreflight{}, &HostPreflightList{})
}
+13
View File
@@ -0,0 +1,13 @@
package v1beta2
type SingleOutcome struct {
When string `json:"when,omitempty" yaml:"when,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
URI string `json:"uri,omitempty" yaml:"uri,omitempty"`
}
type Outcome struct {
Fail *SingleOutcome `json:"fail,omitempty" yaml:"fail,omitempty"`
Warn *SingleOutcome `json:"warn,omitempty" yaml:"warn,omitempty"`
Pass *SingleOutcome `json:"pass,omitempty" yaml:"pass,omitempty"`
}
@@ -287,6 +287,49 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CPU) DeepCopyInto(out *CPU) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CPU.
func (in *CPU) DeepCopy() *CPU {
if in == nil {
return nil
}
out := new(CPU)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CPUAnalyze) DeepCopyInto(out *CPUAnalyze) {
*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 CPUAnalyze.
func (in *CPUAnalyze) DeepCopy() *CPUAnalyze {
if in == nil {
return nil
}
out := new(CPUAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Ceph) DeepCopyInto(out *Ceph) {
*out = *in
@@ -810,6 +853,49 @@ func (in *DeploymentStatus) DeepCopy() *DeploymentStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DiskUsage) DeepCopyInto(out *DiskUsage) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DiskUsage.
func (in *DiskUsage) DeepCopy() *DiskUsage {
if in == nil {
return nil
}
out := new(DiskUsage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DiskUsageAnalyze) DeepCopyInto(out *DiskUsageAnalyze) {
*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 DiskUsageAnalyze.
func (in *DiskUsageAnalyze) DeepCopy() *DiskUsageAnalyze {
if in == nil {
return nil
}
out := new(DiskUsageAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Distribution) DeepCopyInto(out *Distribution) {
*out = *in
@@ -941,6 +1027,260 @@ func (in *HTTP) DeepCopy() *HTTP {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPLoadBalancer) DeepCopyInto(out *HTTPLoadBalancer) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPLoadBalancer.
func (in *HTTPLoadBalancer) DeepCopy() *HTTPLoadBalancer {
if in == nil {
return nil
}
out := new(HTTPLoadBalancer)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
*out = *in
if in.CPU != nil {
in, out := &in.CPU, &out.CPU
*out = new(CPUAnalyze)
(*in).DeepCopyInto(*out)
}
if in.TCPLoadBalancer != nil {
in, out := &in.TCPLoadBalancer, &out.TCPLoadBalancer
*out = new(TCPLoadBalancerAnalyze)
(*in).DeepCopyInto(*out)
}
if in.DiskUsage != nil {
in, out := &in.DiskUsage, &out.DiskUsage
*out = new(DiskUsageAnalyze)
(*in).DeepCopyInto(*out)
}
if in.Memory != nil {
in, out := &in.Memory, &out.Memory
*out = new(MemoryAnalyze)
(*in).DeepCopyInto(*out)
}
if in.TCPPortStatus != nil {
in, out := &in.TCPPortStatus, &out.TCPPortStatus
*out = new(TCPPortStatusAnalyze)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
func (in *HostAnalyze) DeepCopy() *HostAnalyze {
if in == nil {
return nil
}
out := new(HostAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostCollect) DeepCopyInto(out *HostCollect) {
*out = *in
if in.CPU != nil {
in, out := &in.CPU, &out.CPU
*out = new(CPU)
**out = **in
}
if in.Memory != nil {
in, out := &in.Memory, &out.Memory
*out = new(Memory)
**out = **in
}
if in.TCPLoadBalancer != nil {
in, out := &in.TCPLoadBalancer, &out.TCPLoadBalancer
*out = new(TCPLoadBalancer)
**out = **in
}
if in.HTTPLoadBalancer != nil {
in, out := &in.HTTPLoadBalancer, &out.HTTPLoadBalancer
*out = new(HTTPLoadBalancer)
**out = **in
}
if in.TCPPortStatus != nil {
in, out := &in.TCPPortStatus, &out.TCPPortStatus
*out = new(TCPPortStatus)
**out = **in
}
if in.Kubernetes != nil {
in, out := &in.Kubernetes, &out.Kubernetes
*out = new(Kubernetes)
**out = **in
}
if in.IPV4Interfaces != nil {
in, out := &in.IPV4Interfaces, &out.IPV4Interfaces
*out = new(IPV4Interfaces)
**out = **in
}
if in.DiskUsage != nil {
in, out := &in.DiskUsage, &out.DiskUsage
*out = new(DiskUsage)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
func (in *HostCollect) DeepCopy() *HostCollect {
if in == nil {
return nil
}
out := new(HostCollect)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostCollectorMeta) DeepCopyInto(out *HostCollectorMeta) {
*out = *in
out.Exclude = in.Exclude
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollectorMeta.
func (in *HostCollectorMeta) DeepCopy() *HostCollectorMeta {
if in == nil {
return nil
}
out := new(HostCollectorMeta)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPreflight) DeepCopyInto(out *HostPreflight) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPreflight.
func (in *HostPreflight) DeepCopy() *HostPreflight {
if in == nil {
return nil
}
out := new(HostPreflight)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HostPreflight) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPreflightList) DeepCopyInto(out *HostPreflightList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]HostPreflight, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPreflightList.
func (in *HostPreflightList) DeepCopy() *HostPreflightList {
if in == nil {
return nil
}
out := new(HostPreflightList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HostPreflightList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPreflightSpec) DeepCopyInto(out *HostPreflightSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*HostCollect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(HostCollect)
(*in).DeepCopyInto(*out)
}
}
}
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*HostAnalyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(HostAnalyze)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPreflightSpec.
func (in *HostPreflightSpec) DeepCopy() *HostPreflightSpec {
if in == nil {
return nil
}
out := new(HostPreflightSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPreflightStatus) DeepCopyInto(out *HostPreflightStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPreflightStatus.
func (in *HostPreflightStatus) DeepCopy() *HostPreflightStatus {
if in == nil {
return nil
}
out := new(HostPreflightStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IPV4Interfaces) DeepCopyInto(out *IPV4Interfaces) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPV4Interfaces.
func (in *IPV4Interfaces) DeepCopy() *IPV4Interfaces {
if in == nil {
return nil
}
out := new(IPV4Interfaces)
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
@@ -1017,6 +1357,22 @@ func (in *Ingress) DeepCopy() *Ingress {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Kubernetes) DeepCopyInto(out *Kubernetes) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kubernetes.
func (in *Kubernetes) DeepCopy() *Kubernetes {
if in == nil {
return nil
}
out := new(Kubernetes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LogLimits) DeepCopyInto(out *LogLimits) {
*out = *in
@@ -1064,6 +1420,49 @@ func (in *Logs) DeepCopy() *Logs {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Memory) DeepCopyInto(out *Memory) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memory.
func (in *Memory) DeepCopy() *Memory {
if in == nil {
return nil
}
out := new(Memory)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MemoryAnalyze) DeepCopyInto(out *MemoryAnalyze) {
*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 MemoryAnalyze.
func (in *MemoryAnalyze) DeepCopy() *MemoryAnalyze {
if in == nil {
return nil
}
out := new(MemoryAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeResourceFilters) DeepCopyInto(out *NodeResourceFilters) {
*out = *in
@@ -1769,6 +2168,92 @@ 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 *TCPLoadBalancer) DeepCopyInto(out *TCPLoadBalancer) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPLoadBalancer.
func (in *TCPLoadBalancer) DeepCopy() *TCPLoadBalancer {
if in == nil {
return nil
}
out := new(TCPLoadBalancer)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPLoadBalancerAnalyze) DeepCopyInto(out *TCPLoadBalancerAnalyze) {
*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 TCPLoadBalancerAnalyze.
func (in *TCPLoadBalancerAnalyze) DeepCopy() *TCPLoadBalancerAnalyze {
if in == nil {
return nil
}
out := new(TCPLoadBalancerAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPPortStatus) DeepCopyInto(out *TCPPortStatus) {
*out = *in
out.HostCollectorMeta = in.HostCollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPPortStatus.
func (in *TCPPortStatus) DeepCopy() *TCPPortStatus {
if in == nil {
return nil
}
out := new(TCPPortStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TCPPortStatusAnalyze) DeepCopyInto(out *TCPPortStatusAnalyze) {
*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 TCPPortStatusAnalyze.
func (in *TCPPortStatusAnalyze) DeepCopy() *TCPPortStatusAnalyze {
if in == nil {
return nil
}
out := new(TCPPortStatusAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TextAnalyze) DeepCopyInto(out *TextAnalyze) {
*out = *in
@@ -0,0 +1,141 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeHostPreflights implements HostPreflightInterface
type FakeHostPreflights struct {
Fake *FakeTroubleshootV1beta2
ns string
}
var hostpreflightsResource = schema.GroupVersionResource{Group: "troubleshoot.sh", Version: "v1beta2", Resource: "hostpreflights"}
var hostpreflightsKind = schema.GroupVersionKind{Group: "troubleshoot.sh", Version: "v1beta2", Kind: "HostPreflight"}
// Get takes name of the hostPreflight, and returns the corresponding hostPreflight object, and an error if there is any.
func (c *FakeHostPreflights) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.HostPreflight, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(hostpreflightsResource, c.ns, name), &v1beta2.HostPreflight{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.HostPreflight), err
}
// List takes label and field selectors, and returns the list of HostPreflights that match those selectors.
func (c *FakeHostPreflights) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.HostPreflightList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(hostpreflightsResource, hostpreflightsKind, c.ns, opts), &v1beta2.HostPreflightList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta2.HostPreflightList{ListMeta: obj.(*v1beta2.HostPreflightList).ListMeta}
for _, item := range obj.(*v1beta2.HostPreflightList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested hostPreflights.
func (c *FakeHostPreflights) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(hostpreflightsResource, c.ns, opts))
}
// Create takes the representation of a hostPreflight and creates it. Returns the server's representation of the hostPreflight, and an error, if there is any.
func (c *FakeHostPreflights) Create(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.CreateOptions) (result *v1beta2.HostPreflight, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(hostpreflightsResource, c.ns, hostPreflight), &v1beta2.HostPreflight{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.HostPreflight), err
}
// Update takes the representation of a hostPreflight and updates it. Returns the server's representation of the hostPreflight, and an error, if there is any.
func (c *FakeHostPreflights) Update(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (result *v1beta2.HostPreflight, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(hostpreflightsResource, c.ns, hostPreflight), &v1beta2.HostPreflight{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.HostPreflight), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeHostPreflights) UpdateStatus(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (*v1beta2.HostPreflight, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(hostpreflightsResource, "status", c.ns, hostPreflight), &v1beta2.HostPreflight{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.HostPreflight), err
}
// Delete takes name of the hostPreflight and deletes it. Returns an error if one occurs.
func (c *FakeHostPreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(hostpreflightsResource, c.ns, name), &v1beta2.HostPreflight{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeHostPreflights) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(hostpreflightsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta2.HostPreflightList{})
return err
}
// Patch applies the patch and returns the patched hostPreflight.
func (c *FakeHostPreflights) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.HostPreflight, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(hostpreflightsResource, c.ns, name, pt, data, subresources...), &v1beta2.HostPreflight{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.HostPreflight), err
}
@@ -35,6 +35,10 @@ func (c *FakeTroubleshootV1beta2) Collectors(namespace string) v1beta2.Collector
return &FakeCollectors{c, namespace}
}
func (c *FakeTroubleshootV1beta2) HostPreflights(namespace string) v1beta2.HostPreflightInterface {
return &FakeHostPreflights{c, namespace}
}
func (c *FakeTroubleshootV1beta2) Preflights(namespace string) v1beta2.PreflightInterface {
return &FakePreflights{c, namespace}
}
@@ -21,6 +21,8 @@ type AnalyzerExpansion interface{}
type CollectorExpansion interface{}
type HostPreflightExpansion interface{}
type PreflightExpansion interface{}
type RedactorExpansion interface{}
@@ -0,0 +1,194 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta2
import (
"context"
"time"
v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
scheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// HostPreflightsGetter has a method to return a HostPreflightInterface.
// A group's client should implement this interface.
type HostPreflightsGetter interface {
HostPreflights(namespace string) HostPreflightInterface
}
// HostPreflightInterface has methods to work with HostPreflight resources.
type HostPreflightInterface interface {
Create(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.CreateOptions) (*v1beta2.HostPreflight, error)
Update(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (*v1beta2.HostPreflight, error)
UpdateStatus(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (*v1beta2.HostPreflight, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.HostPreflight, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta2.HostPreflightList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.HostPreflight, err error)
HostPreflightExpansion
}
// hostPreflights implements HostPreflightInterface
type hostPreflights struct {
client rest.Interface
ns string
}
// newHostPreflights returns a HostPreflights
func newHostPreflights(c *TroubleshootV1beta2Client, namespace string) *hostPreflights {
return &hostPreflights{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the hostPreflight, and returns the corresponding hostPreflight object, and an error if there is any.
func (c *hostPreflights) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.HostPreflight, err error) {
result = &v1beta2.HostPreflight{}
err = c.client.Get().
Namespace(c.ns).
Resource("hostpreflights").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of HostPreflights that match those selectors.
func (c *hostPreflights) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.HostPreflightList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta2.HostPreflightList{}
err = c.client.Get().
Namespace(c.ns).
Resource("hostpreflights").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested hostPreflights.
func (c *hostPreflights) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("hostpreflights").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a hostPreflight and creates it. Returns the server's representation of the hostPreflight, and an error, if there is any.
func (c *hostPreflights) Create(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.CreateOptions) (result *v1beta2.HostPreflight, err error) {
result = &v1beta2.HostPreflight{}
err = c.client.Post().
Namespace(c.ns).
Resource("hostpreflights").
VersionedParams(&opts, scheme.ParameterCodec).
Body(hostPreflight).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a hostPreflight and updates it. Returns the server's representation of the hostPreflight, and an error, if there is any.
func (c *hostPreflights) Update(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (result *v1beta2.HostPreflight, err error) {
result = &v1beta2.HostPreflight{}
err = c.client.Put().
Namespace(c.ns).
Resource("hostpreflights").
Name(hostPreflight.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(hostPreflight).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *hostPreflights) UpdateStatus(ctx context.Context, hostPreflight *v1beta2.HostPreflight, opts v1.UpdateOptions) (result *v1beta2.HostPreflight, err error) {
result = &v1beta2.HostPreflight{}
err = c.client.Put().
Namespace(c.ns).
Resource("hostpreflights").
Name(hostPreflight.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(hostPreflight).
Do(ctx).
Into(result)
return
}
// Delete takes name of the hostPreflight and deletes it. Returns an error if one occurs.
func (c *hostPreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("hostpreflights").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *hostPreflights) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("hostpreflights").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched hostPreflight.
func (c *hostPreflights) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.HostPreflight, err error) {
result = &v1beta2.HostPreflight{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("hostpreflights").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
@@ -27,6 +27,7 @@ type TroubleshootV1beta2Interface interface {
RESTClient() rest.Interface
AnalyzersGetter
CollectorsGetter
HostPreflightsGetter
PreflightsGetter
RedactorsGetter
SupportBundlesGetter
@@ -45,6 +46,10 @@ func (c *TroubleshootV1beta2Client) Collectors(namespace string) CollectorInterf
return newCollectors(c, namespace)
}
func (c *TroubleshootV1beta2Client) HostPreflights(namespace string) HostPreflightInterface {
return newHostPreflights(c, namespace)
}
func (c *TroubleshootV1beta2Client) Preflights(namespace string) PreflightInterface {
return newPreflights(c, namespace)
}
+44
View File
@@ -0,0 +1,44 @@
package collect
import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
type HostCollector struct {
Collect *troubleshootv1beta2.HostCollect
}
type HostCollectors []*HostCollector
func (c *HostCollector) RunCollectorSync() (result map[string][]byte, err error) {
defer func() {
if r := recover(); r != nil {
err = errors.Errorf("recovered rom panic: %v", r)
}
}()
if c.Collect.CPU != nil {
result, err = HostCPU(c)
} else if c.Collect.Memory != nil {
result, err = HostMemory(c)
} else if c.Collect.TCPLoadBalancer != nil {
result, err = HostTCPLoadBalancer(c)
} else if c.Collect.DiskUsage != nil {
result, err = HostDiskUsage(c)
} else if c.Collect.TCPPortStatus != nil {
result, err = HostTCPPortStatus(c)
} else {
err = errors.New("no spec found to run")
return
}
if err != nil {
return
}
return
}
func (c *HostCollector) GetDisplayName() string {
return c.Collect.GetName()
}
+38
View File
@@ -0,0 +1,38 @@
package collect
import (
"encoding/json"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/cpu"
)
type CPUInfo struct {
LogicalCount int `json:"logicalCount"`
PhysicalCount int `json:"physicalCount"`
}
func HostCPU(c *HostCollector) (map[string][]byte, error) {
cpuInfo := CPUInfo{}
logicalCount, err := cpu.Counts(true)
if err != nil {
return nil, errors.Wrap(err, "failed to count logical cpus")
}
cpuInfo.LogicalCount = logicalCount
physicalCount, err := cpu.Counts(false)
if err != nil {
return nil, errors.Wrap(err, "failed to count physical cpus")
}
cpuInfo.PhysicalCount = physicalCount
b, err := json.Marshal(cpuInfo)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal cpu info")
}
return map[string][]byte{
"system/cpu.json": b,
}, nil
}
+43
View File
@@ -0,0 +1,43 @@
package collect
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/disk"
)
type DiskUsageInfo struct {
TotalBytes uint64 `json:"total_bytes"`
UsedBytes uint64 `json:"used_bytes"`
}
func HostDiskUsage(c *HostCollector) (map[string][]byte, error) {
result := map[string][]byte{}
if c.Collect.DiskUsage == nil {
return result, nil
}
du, err := disk.Usage(c.Collect.DiskUsage.Path)
if err != nil {
return result, errors.Wrapf(err, "collect disk usage for %s", c.Collect.DiskUsage.Path)
}
diskSpaceInfo := DiskUsageInfo{
TotalBytes: du.Total,
UsedBytes: du.Used,
}
b, err := json.Marshal(diskSpaceInfo)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal disk space info")
}
key := HostDiskUsageKey(c.Collect.DiskUsage.CollectorName)
result[key] = b
return result, nil
}
func HostDiskUsageKey(name string) string {
return fmt.Sprintf("diskUsage/%s.json", name)
}
+31
View File
@@ -0,0 +1,31 @@
package collect
import (
"encoding/json"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/mem"
)
type MemoryInfo struct {
Total uint64 `json:"total"`
}
func HostMemory(c *HostCollector) (map[string][]byte, error) {
memoryInfo := MemoryInfo{}
vmstat, err := mem.VirtualMemory()
if err != nil {
return nil, errors.Wrap(err, "failed to read virtual memory")
}
memoryInfo.Total = vmstat.Available
b, err := json.Marshal(memoryInfo)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal memory info")
}
return map[string][]byte{
"system/memory.json": b,
}, nil
}
+156
View File
@@ -0,0 +1,156 @@
package collect
import (
"bytes"
"fmt"
"net"
"strings"
"time"
"github.com/pkg/errors"
"github.com/segmentio/ksuid"
)
type NetworkStatus string
const (
NetworkStatusAddressInUse = "address-in-use"
NetworkStatusConnectionRefused = "connection-refused"
NetworkStatusConnectionTimeout = "connection-timeout"
NetworkStatusConnected = "connected"
NetworkStatusErrorOther = "error"
)
type NetworkStatusResult struct {
Status NetworkStatus `json:"status"`
}
func checkTCPConnection(listenAddress string, dialAddress string, timeout time.Duration) (NetworkStatus, error) {
lstn, err := net.Listen("tcp", listenAddress)
if err != nil {
if strings.Contains(err.Error(), "address already in use") {
return NetworkStatusAddressInUse, nil
}
return NetworkStatusErrorOther, errors.Wrap(err, "failed to create listener")
}
defer lstn.Close()
// The server may receive requests from other clients and the client request may be forwarded to
// other servers. The client must continue to initiate new connections and send its request
// token until the server responds with its token.
requestToken := ksuid.New().Bytes()
responseToken := ksuid.New().Bytes()
go func() {
for {
conn, err := lstn.Accept()
if err != nil {
continue
}
if handleTestConnection(conn, requestToken, responseToken) {
return
}
}
}()
stopAfter := time.Now().Add(timeout)
for {
if time.Now().After(stopAfter) {
return NetworkStatusConnectionTimeout, nil
}
conn, err := net.DialTimeout("tcp", dialAddress, 50*time.Millisecond)
if err != nil {
if strings.Contains(err.Error(), "i/o timeout") {
time.Sleep(time.Millisecond * 50)
continue
}
if strings.Contains(err.Error(), "connection refused") {
return NetworkStatusConnectionRefused, nil
}
return NetworkStatusErrorOther, errors.Wrap(err, "failed to dial")
}
if verifyConnectionToServer(conn, requestToken, responseToken) {
return NetworkStatusConnected, nil
}
time.Sleep(time.Millisecond * 50)
}
}
func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []byte) bool {
defer func() {
if err := conn.Close(); err != nil {
fmt.Println(err.Error())
}
}()
if err := conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
fmt.Printf("Server failed to set read deadline: %v", err)
return false
}
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
fmt.Printf("Server failed to read: %v", err)
return false
}
if !bytes.Contains(buf, requestToken) {
return false
}
if err := conn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
fmt.Printf("Server failed to set write deadline: %v", err)
return false
}
if _, err := conn.Write(responseToken); err != nil {
fmt.Printf("Server failed to write: %v", err)
return false
}
return true
}
func verifyConnectionToServer(conn net.Conn, requestToken []byte, responseToken []byte) bool {
defer func() {
if err := conn.Close(); err != nil {
fmt.Println(err.Error())
}
}()
if err := conn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
fmt.Printf("Client failed to set write deadline: %v", err)
return false
}
_, err := conn.Write(requestToken)
if err != nil {
fmt.Printf("Client failed to write: %v", err)
return false
}
if err := conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
fmt.Printf("Client failed to set read deadline: %v", err)
return false
}
buf := make([]byte, 1024)
_, err = conn.Read(buf)
if err != nil {
fmt.Printf("Client failed to read: %v", err)
return false
}
if !bytes.Contains(buf, responseToken) {
return false
}
return true
}
+57
View File
@@ -0,0 +1,57 @@
package collect
import (
"encoding/json"
"fmt"
"path"
"time"
"github.com/pkg/errors"
)
type ConnectionResult int
const (
ConnectionRefused ConnectionResult = iota
Connected
ConnectionTimeout
ConnectionAddressInUse
ErrorOther
)
func HostTCPLoadBalancer(c *HostCollector) (map[string][]byte, error) {
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.Collect.TCPLoadBalancer.Port)
dialAddress := c.Collect.TCPLoadBalancer.Address
timeout := 60 * time.Minute
if c.Collect.TCPLoadBalancer.Timeout != "" {
var err error
timeout, err = time.ParseDuration(c.Collect.TCPLoadBalancer.Timeout)
if err != nil {
return nil, errors.Wrap(err, "failed to parse durection")
}
}
networkStatus, err := checkTCPConnection(listenAddress, dialAddress, timeout)
if err != nil {
return nil, err
}
result := NetworkStatusResult{
Status: networkStatus,
}
b, err := json.Marshal(result)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal result")
}
name := path.Join("tcpLoadBalancer", "tcpLoadBalancer.json")
if c.Collect.TCPLoadBalancer.CollectorName != "" {
name = path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", c.Collect.TCPLoadBalancer.CollectorName))
}
return map[string][]byte{
name: b,
}, nil
}
+100
View File
@@ -0,0 +1,100 @@
package collect
import (
"encoding/json"
"fmt"
"net"
"path"
"time"
"github.com/pkg/errors"
)
func HostTCPPortStatus(c *HostCollector) (map[string][]byte, error) {
dialAddress := ""
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.Collect.TCPPortStatus.Port)
if c.Collect.TCPPortStatus.Interface != "" {
iface, err := net.InterfaceByName(c.Collect.TCPPortStatus.Interface)
if err != nil {
return nil, errors.Wrapf(err, "lookup interface %s", c.Collect.TCPPortStatus.Interface)
}
ip, err := getIPv4FromInterface(iface)
if err != nil {
return nil, errors.Wrapf(err, "get ipv4 address for interface %s", c.Collect.TCPPortStatus.Interface)
}
listenAddress = fmt.Sprintf("%s:%d", ip, c.Collect.TCPPortStatus.Port)
dialAddress = listenAddress
}
if dialAddress == "" {
ip, err := getLocalIPv4()
if err != nil {
return nil, err
}
dialAddress = fmt.Sprintf("%s:%d", ip, c.Collect.TCPPortStatus.Port)
}
networkStatus, err := checkTCPConnection(listenAddress, dialAddress, 10*time.Second)
if err != nil {
return nil, err
}
result := NetworkStatusResult{
Status: networkStatus,
}
b, err := json.Marshal(result)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal result")
}
name := path.Join("tcpPortStatus", "tcpPortStatus.json")
if c.Collect.TCPPortStatus.CollectorName != "" {
name = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", c.Collect.TCPPortStatus.CollectorName))
}
return map[string][]byte{
name: b,
}, nil
}
func getIPv4FromInterface(iface *net.Interface) (net.IP, error) {
addrs, err := iface.Addrs()
if err != nil {
return nil, errors.Wrap(err, "list interface addresses")
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
return nil, errors.Wrapf(err, "parse interface address %q", addr.String())
}
ip = ip.To4()
if ip != nil {
return ip, nil
}
}
return nil, errors.New("interface does not have an ipv4 address")
}
func getLocalIPv4() (net.IP, error) {
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 {
return ip, nil
}
}
return nil, errors.New("No network interface has an IPv4 address")
}
+31 -5
View File
@@ -6,12 +6,22 @@ import (
"strings"
analyze "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
// Analyze runs the analyze phase of preflight checks
func (c CollectResult) Analyze() []*analyze.AnalyzeResult {
func (c ClusterCollectResult) Analyze() []*analyze.AnalyzeResult {
return doAnalyze(c.AllCollectedData, c.Spec.Spec.Analyzers, nil)
}
// Analyze runs the analysze phase of host preflight checks
func (c HostCollectResult) Analyze() []*analyze.AnalyzeResult {
return doAnalyze(c.AllCollectedData, nil, c.Spec.Spec.Analyzers)
}
func doAnalyze(allCollectedData map[string][]byte, analyzers []*troubleshootv1beta2.Analyze, hostAnalyzers []*troubleshootv1beta2.HostAnalyze) []*analyze.AnalyzeResult {
getCollectedFileContents := func(fileName string) ([]byte, error) {
contents, ok := c.AllCollectedData[fileName]
contents, ok := allCollectedData[fileName]
if !ok {
return nil, fmt.Errorf("file %s was not collected", fileName)
}
@@ -20,13 +30,13 @@ func (c CollectResult) Analyze() []*analyze.AnalyzeResult {
}
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range c.AllCollectedData {
for k, v := range allCollectedData {
if strings.HasPrefix(k, prefix) {
matching[k] = v
}
}
for k, v := range c.AllCollectedData {
for k, v := range allCollectedData {
if ok, _ := filepath.Match(prefix, k); ok {
matching[k] = v
}
@@ -36,7 +46,7 @@ func (c CollectResult) Analyze() []*analyze.AnalyzeResult {
}
analyzeResults := []*analyze.AnalyzeResult{}
for _, analyzer := range c.Spec.Spec.Analyzers {
for _, analyzer := range analyzers {
analyzeResult, err := analyze.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
analyzeResult = []*analyze.AnalyzeResult{
@@ -53,5 +63,21 @@ func (c CollectResult) Analyze() []*analyze.AnalyzeResult {
}
}
for _, hostAnalyzer := range hostAnalyzers {
analyzeResult, err := analyze.HostAnalyze(hostAnalyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
analyzeResult = []*analyze.AnalyzeResult{
{
IsFail: true,
Title: "Analyzer Failed",
Message: err.Error(),
},
}
}
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult...)
}
}
return analyzeResults
}
+79 -5
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/pkg/errors"
analyze "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/client-go/rest"
@@ -17,13 +18,73 @@ type CollectOpts struct {
ProgressChan chan interface{}
}
type CollectResult struct {
type CollectResult interface {
Analyze() []*analyze.AnalyzeResult
IsRBACAllowed() bool
}
type ClusterCollectResult struct {
AllCollectedData map[string][]byte
Collectors collect.Collectors
IsRBACAllowed bool
isRBACAllowed bool
Spec *troubleshootv1beta2.Preflight
}
func (cr ClusterCollectResult) IsRBACAllowed() bool {
return cr.isRBACAllowed
}
type HostCollectResult struct {
AllCollectedData map[string][]byte
Collectors collect.HostCollectors
Spec *troubleshootv1beta2.HostPreflight
}
func (cr HostCollectResult) IsRBACAllowed() bool {
return true
}
// CollectHost runs the collection phase of host preflight checks
func CollectHost(opts CollectOpts, p *troubleshootv1beta2.HostPreflight) (CollectResult, error) {
collectSpecs := make([]*troubleshootv1beta2.HostCollect, 0, 0)
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
collectSpecs = ensureHostCollectorInList(collectSpecs, troubleshootv1beta2.HostCollect{CPU: &troubleshootv1beta2.CPU{}})
collectSpecs = ensureHostCollectorInList(collectSpecs, troubleshootv1beta2.HostCollect{Memory: &troubleshootv1beta2.Memory{}})
allCollectedData := make(map[string][]byte)
var collectors collect.HostCollectors
for _, desiredCollector := range collectSpecs {
collector := collect.HostCollector{
Collect: desiredCollector,
}
collectors = append(collectors, &collector)
}
collectResult := HostCollectResult{
Collectors: collectors,
Spec: p,
}
for _, collector := range collectors {
result, err := collector.RunCollectorSync()
if err != nil {
opts.ProgressChan <- errors.Errorf("failed to run collector: %s: %v\n", collector.GetDisplayName(), err)
continue
}
if result != nil {
for k, v := range result {
allCollectedData[k] = v
}
}
}
collectResult.AllCollectedData = allCollectedData
return collectResult, nil
}
// Collect runs the collection phase of preflight checks
func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult, error) {
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
@@ -44,7 +105,7 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
collectors = append(collectors, &collector)
}
collectResult := CollectResult{
collectResult := ClusterCollectResult{
Collectors: collectors,
Spec: p,
}
@@ -62,7 +123,7 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
}
if foundForbidden && !opts.IgnorePermissionErrors {
collectResult.IsRBACAllowed = false
collectResult.isRBACAllowed = false
return collectResult, errors.New("insufficient permissions to run all collectors")
}
@@ -71,7 +132,7 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
if len(collector.RBACErrors) > 0 {
// don't skip clusterResources collector due to RBAC issues
if collector.Collect.ClusterResources == nil {
collectResult.IsRBACAllowed = false // not failing, but going to report this
collectResult.isRBACAllowed = false // not failing, but going to report this
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
continue
}
@@ -106,3 +167,16 @@ func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubl
return append(list, &collector)
}
func ensureHostCollectorInList(list []*troubleshootv1beta2.HostCollect, collector troubleshootv1beta2.HostCollect) []*troubleshootv1beta2.HostCollect {
for _, inList := range list {
if collector.CPU != nil && inList.CPU != nil {
return list
}
if collector.Memory != nil && inList.Memory != nil {
return list
}
}
return append(list, &collector)
}