mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae63ccba8d | ||
|
|
5c5e30a9a5 | ||
|
|
ac2a0484e2 | ||
|
|
bc91151883 | ||
|
|
de8feb167e | ||
|
|
0a6ef1c2c3 | ||
|
|
4a89067912 | ||
|
|
1e17c5c4c2 | ||
|
|
32cca1215a | ||
|
|
052d10fec7 | ||
|
|
7a0c6e5383 | ||
|
|
1dc03db254 | ||
|
|
fe414af556 | ||
|
|
f8dca39899 | ||
|
|
19aef8a02f | ||
|
|
4b6606e323 | ||
|
|
559e18d996 | ||
|
|
bf4d26acd2 | ||
|
|
f3b599c19a | ||
|
|
810b3cbf78 | ||
|
|
81789181ed | ||
|
|
256c68feca | ||
|
|
b01e6ef578 | ||
|
|
62afc87af8 | ||
|
|
17bff4b2bd | ||
|
|
5f2525b663 | ||
|
|
afa0bc56d4 | ||
|
|
c1f9b3906d | ||
|
|
0a19d35073 | ||
|
|
7c4135d563 | ||
|
|
4b78c430ca | ||
|
|
09d16ff185 | ||
|
|
bdf843f84d | ||
|
|
47f7d98907 | ||
|
|
a1e0813d55 | ||
|
|
d6acd6d906 | ||
|
|
ce53dbcd29 | ||
|
|
3b10d9c9e1 | ||
|
|
7589b4fed0 | ||
|
|
a459120516 | ||
|
|
b02338762e | ||
|
|
76419237d4 | ||
|
|
87b4c12274 | ||
|
|
7647c039e9 | ||
|
|
c26824a619 | ||
|
|
617e0d55de | ||
|
|
b418334a46 | ||
|
|
b78d7d04d9 | ||
|
|
989d5f7dbd | ||
|
|
8aca23199c | ||
|
|
fe4db40b43 | ||
|
|
5eef177965 | ||
|
|
6498c34da5 | ||
|
|
b0a005796c | ||
|
|
1311064d21 | ||
|
|
450d7570eb | ||
|
|
40af0f8a9c | ||
|
|
529fe8cdbf | ||
|
|
105a718bbb | ||
|
|
c2b53036f9 | ||
|
|
ba22fe9f22 | ||
|
|
8df8a5c792 | ||
|
|
0bcd5183f5 | ||
|
|
e2c0c722ae | ||
|
|
9984fe2caa | ||
|
|
f2e4127111 | ||
|
|
f25149f45c |
@@ -35,27 +35,29 @@ define LDFLAGS
|
||||
"
|
||||
endef
|
||||
|
||||
BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" -installsuffix netgo
|
||||
|
||||
all: test
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
go build ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet
|
||||
go test ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
go test ${BUILDFLAGS} ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@@ -63,7 +65,7 @@ fmt:
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./pkg/... ./cmd/...
|
||||
go vet ${BUILDFLAGS} ./pkg/... ./cmd/...
|
||||
|
||||
.PHONY: generate
|
||||
generate: controller-gen client-gen
|
||||
|
||||
+43
-22
@@ -91,30 +91,51 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressCh := make(chan interface{}, 0) // non-zero buffer will result in missed messages
|
||||
|
||||
s := spin.New()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-progressCh:
|
||||
if !ok {
|
||||
continue
|
||||
if v.GetBool("interactive") {
|
||||
s := spin.New()
|
||||
go func() {
|
||||
lastMsg := ""
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-progressCh:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
if lastMsg == msg {
|
||||
break
|
||||
}
|
||||
lastMsg = msg
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println(fmt.Sprintf("%s\r * %s", cursor.ClearEntireLine(), msg))
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println(fmt.Sprintf("%s\r * %s", cursor.ClearEntireLine(), msg))
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
} else {
|
||||
// make sure we don't block any senders
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-progressCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case <-finishedCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
close(finishedCh)
|
||||
|
||||
@@ -19,6 +19,7 @@ func showStdoutResults(format string, preflightName string, analyzeResults []*an
|
||||
}
|
||||
|
||||
func showStdoutResultsHuman(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
fmt.Println("")
|
||||
var failed bool
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
testResultfailed := outputResult(analyzeResult)
|
||||
|
||||
@@ -38,6 +38,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
|
||||
@@ -55,7 +55,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
collectorContent, err := loadSpec(v, arg)
|
||||
collectorContent, err := loadSupportBundleSpec(v, arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorContent, err := loadSpec(v, redactor)
|
||||
redactorContent, err := loadRedactorSpec(v, redactor)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to load redactor spec #%d", idx)
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
c.Printf("%s\r * Failed to analyze support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
interactive := isatty.IsTerminal(os.Stdout.Fd())
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
if interactive {
|
||||
close(finishedCh) // this removes the spinner
|
||||
@@ -247,13 +247,12 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
var err error
|
||||
func loadSupportBundleSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, errors.Errorf("path %s must have 3 components", arg)
|
||||
return nil, errors.Errorf("secret path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "support-bundle-spec")
|
||||
@@ -264,6 +263,43 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
return loadSpec(v, arg)
|
||||
}
|
||||
|
||||
func loadRedactorSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "configmap/") {
|
||||
// format configmap/namespace-name/configmap-name[/data-key]
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, errors.Errorf("configmap path %s must have at most 4 components", arg)
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, errors.Errorf("configmap path %s must have at least 3 components", arg)
|
||||
}
|
||||
|
||||
dataKey := "redactor-spec"
|
||||
if len(pathParts) == 4 {
|
||||
dataKey = pathParts[3]
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromConfigMap(pathParts[1], pathParts[2], dataKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from configmap")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
spec, err := loadSpec(v, arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load spec")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
var err error
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
@@ -413,7 +449,6 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta2.Collect, ad
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
Namespace: v.GetString("namespace"),
|
||||
PathPrefix: filepath.Base(bundlePath),
|
||||
}
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
@@ -464,8 +499,7 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta2.Collect, ad
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
// results already contain the bundle dir name in their paths
|
||||
err = saveCollectorOutput(result, filepath.Dir(bundlePath), collector)
|
||||
err = saveCollectorOutput(result, bundlePath, collector)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
|
||||
+2
-19
@@ -4,18 +4,6 @@ release:
|
||||
owner: replicatedhq
|
||||
name: troubleshoot
|
||||
builds:
|
||||
- id: so
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GO111MODULE=on
|
||||
main: ffi/main.go
|
||||
flags: -buildmode=c-shared
|
||||
binary: troubleshoot.so
|
||||
hooks: {}
|
||||
- id: preflight
|
||||
goos:
|
||||
- linux
|
||||
@@ -31,7 +19,7 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
flags: -tags netgo -tags containers_image_ostree_stub -tags exclude_graphdriver_devicemapper -tags exclude_graphdriver_btrfs -tags containers_image_openpgp -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
- id: support-bundle
|
||||
@@ -49,15 +37,10 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
flags: -tags netgo -tags containers_image_ostree_stub -tags exclude_graphdriver_devicemapper -tags exclude_graphdriver_btrfs -tags containers_image_openpgp -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
archives:
|
||||
- id: so
|
||||
builds:
|
||||
- so
|
||||
format: tar.gz
|
||||
name_template: 'troubleshoot.so_{{ .Os }}_{{ .Arch }}'
|
||||
- id: preflight
|
||||
builds:
|
||||
- preflight
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: block
|
||||
spec:
|
||||
collectors:
|
||||
- blockDevices: {}
|
||||
analyzers:
|
||||
- blockDevices:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: ".* == 1"
|
||||
message: One available block device
|
||||
- pass:
|
||||
when: ".* > 1"
|
||||
message: Multiple available block devices
|
||||
- fail:
|
||||
message: No available block devices
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: certificate
|
||||
spec:
|
||||
collectors:
|
||||
- certificate:
|
||||
certificatePath: /etc/ssl/corp.crt
|
||||
keyPath: /etc/ssl/corp.key
|
||||
analyzers:
|
||||
- certificate:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "key-pair-missing"
|
||||
message: Certificate key pair not found in /etc/ssl
|
||||
- fail:
|
||||
when: "key-pair-switched"
|
||||
message: Cert and key pair are switched
|
||||
- fail:
|
||||
when: "key-pair-encrypted"
|
||||
message: Private key is encrypted
|
||||
- fail:
|
||||
when: "key-pair-mismatch"
|
||||
message: Cert and key do not match
|
||||
- fail:
|
||||
when: "key-pair-invalid"
|
||||
message: Certificate key pair is invalid
|
||||
- pass:
|
||||
when: "key-pair-valid"
|
||||
message: Certificate key pair is valid
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: fsperf
|
||||
spec:
|
||||
collectors:
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
directory: /var/lib/etcd
|
||||
fileSize: 22Mi
|
||||
operationSizeBytes: 2300
|
||||
datasync: true
|
||||
analyzers:
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "iops < 50"
|
||||
message: Insufficient random read IOPS
|
||||
- pass:
|
||||
when: "p99 < 3ms"
|
||||
message: Write latency is great!
|
||||
- pass:
|
||||
when: "p99 < 5ms"
|
||||
message: Write latency is ok
|
||||
- warn:
|
||||
when: "p99 < 8ms"
|
||||
message: Write latency is high
|
||||
- fail:
|
||||
when: "p99 >= 8ms"
|
||||
message: Write latency is too high
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: httploadbalancer
|
||||
spec:
|
||||
collectors:
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
port: 80
|
||||
address: http://app.corporate.internal
|
||||
timeout: 10s
|
||||
analyzers:
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 80 via load balancer was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 80.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 80 via load balancer. Check your firewall.
|
||||
- fail:
|
||||
when: "bind-permission-denied"
|
||||
message: Bind permission denied. Try running as root.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Failed to connect to port 80 via load balancer.
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to port 80 via load balancer.
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: http
|
||||
spec:
|
||||
collectors:
|
||||
- http:
|
||||
collectorName: registry
|
||||
get:
|
||||
url: https://registry.replicated.com
|
||||
analyzers:
|
||||
- http:
|
||||
collectorName: registry
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Error connecting to registry
|
||||
- pass:
|
||||
when: "statusCode == 404"
|
||||
message: Connected to registry
|
||||
- fail:
|
||||
message: "Unexpected response"
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: ipv4Interfaces
|
||||
spec:
|
||||
collectors:
|
||||
- ipv4Interfaces: {}
|
||||
analyzers:
|
||||
- ipv4Interfaces:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count == 0"
|
||||
message: No IPv4 interfaces detected
|
||||
- warn:
|
||||
when: "count >= 2"
|
||||
message: Multiple IPv4 interfaces detected
|
||||
- pass:
|
||||
when: "count == 1"
|
||||
message: IPv4 interface detected
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: ntp
|
||||
spec:
|
||||
collectors:
|
||||
- time: {}
|
||||
analyzers:
|
||||
- time:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "ntp == unsynchronized+inactive"
|
||||
message: System clock not synchronized
|
||||
- warn:
|
||||
when: "ntp == unsynchronized+active"
|
||||
message: System clock not yet synchronized
|
||||
- warn:
|
||||
when: "ntp == synchronized+inactive"
|
||||
message: NTP not active
|
||||
- pass:
|
||||
when: "ntp == synchronized+active"
|
||||
message: System clock is synchronized
|
||||
@@ -0,0 +1,238 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- blockDevices: {}
|
||||
- certificate:
|
||||
certificatePath: /etc/ssl/corp.crt
|
||||
keyPath: /etc/ssl/corp.key
|
||||
- cpu: {}
|
||||
- diskUsage:
|
||||
collectorName: ephemeral
|
||||
path: /var/lib/kubelet
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
directory: /var/lib/etcd
|
||||
fileSize: 22Mi
|
||||
operationSizeBytes: 2300
|
||||
datasync: true
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
port: 80
|
||||
address: http://app.corporate.internal
|
||||
timeout: 10s
|
||||
- http:
|
||||
collectorName: registry
|
||||
get:
|
||||
url: https://registry.replicated.com
|
||||
- ipv4Interfaces: {}
|
||||
- memory: {}
|
||||
- time: {}
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
address: 10.128.0.2:6783
|
||||
timeout: 2s
|
||||
- tcpLoadBalancer:
|
||||
collectorName: LB1
|
||||
address: 10.128.0.20:6443
|
||||
port: 6443
|
||||
timeout: 5000ms
|
||||
- tcpPortStatus:
|
||||
collectorName: k8s
|
||||
port: 6443
|
||||
analyzers:
|
||||
- blockDevices:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: ".* == 1"
|
||||
message: One available block device
|
||||
- pass:
|
||||
when: ".* > 1"
|
||||
message: Multiple available block devices
|
||||
- fail:
|
||||
message: No available block devices
|
||||
- certificate:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "key-pair-missing"
|
||||
message: Certificate key pair not found in /etc/ssl
|
||||
- fail:
|
||||
when: "key-pair-switched"
|
||||
message: Cert and key pair are switched
|
||||
- fail:
|
||||
when: "key-pair-encrypted"
|
||||
message: Private key is encrypted
|
||||
- fail:
|
||||
when: "key-pair-mismatch"
|
||||
message: Cert and key do not match
|
||||
- fail:
|
||||
when: "key-pair-invalid"
|
||||
message: Certificate key pair is invalid
|
||||
- pass:
|
||||
when: "key-pair-valid"
|
||||
message: Certificate key pair is valid
|
||||
- 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
|
||||
- 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
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "iops < 50"
|
||||
message: Insufficient random read IOPS
|
||||
- pass:
|
||||
when: "p99 < 3ms"
|
||||
message: Write latency is great!
|
||||
- pass:
|
||||
when: "p99 < 5ms"
|
||||
message: Write latency is ok
|
||||
- warn:
|
||||
when: "p99 < 8ms"
|
||||
message: Write latency is high
|
||||
- fail:
|
||||
when: "p99 >= 8ms"
|
||||
message: Write latency is too high
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 80 via load balancer was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 80.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 80 via load balancer. Check your firewall.
|
||||
- fail:
|
||||
when: "bind-permission-denied"
|
||||
message: Bind permission denied. Try running as root.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Failed to connect to port 80 via load balancer.
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to port 80 via load balancer.
|
||||
- http:
|
||||
collectorName: registry
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Error connecting to registry
|
||||
- pass:
|
||||
when: "statusCode == 404"
|
||||
message: Connected to registry
|
||||
- fail:
|
||||
message: "Unexpected response"
|
||||
- ipv4Interfaces:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count == 0"
|
||||
message: No IPv4 interfaces detected
|
||||
- warn:
|
||||
when: "count >= 2"
|
||||
message: Multiple IPv4 interfaces detected
|
||||
- pass:
|
||||
when: "count == 1"
|
||||
message: IPv4 interface detected
|
||||
- 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
|
||||
- time:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "ntp == unsynchronized+inactive"
|
||||
message: System clock not synchronized
|
||||
- warn:
|
||||
when: "ntp == unsynchronized+active"
|
||||
message: System clock not yet synchronized
|
||||
- warn:
|
||||
when: "ntp == synchronized+inactive"
|
||||
message: NTP not active
|
||||
- pass:
|
||||
when: "ntp == synchronized+active"
|
||||
message: System clock is synchronized
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to weave on host 1 was refused
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to weave on host 1
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected error connecting to weave on host 1
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to weave on host 1
|
||||
- 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.
|
||||
- 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
|
||||
- time:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "timezone == UTC"
|
||||
message: Timezone is UTC
|
||||
- fail:
|
||||
message: Timezone is not UTC
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: connect
|
||||
spec:
|
||||
collectors:
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
address: 10.128.0.2:6783
|
||||
analyzers:
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to weave on host 1 was refused
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to weave on host 1
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected error connecting to weave on host 1
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to weave on host 1
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: timezone
|
||||
spec:
|
||||
collectors:
|
||||
- time: {}
|
||||
analyzers:
|
||||
- time:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "timezone == UTC"
|
||||
message: Timezone is UTC
|
||||
- fail:
|
||||
message: Timezone is not UTC
|
||||
@@ -1,51 +0,0 @@
|
||||
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
|
||||
@@ -60,13 +60,18 @@ spec:
|
||||
- pass:
|
||||
when: "== aks"
|
||||
message: AKS is a supported distribution
|
||||
# Will be supported in the future
|
||||
- pass:
|
||||
when: "== kurl"
|
||||
message: KURL is a supported distribution
|
||||
- pass:
|
||||
when: "== digitalocean"
|
||||
message: DigitalOcean is a supported distribution
|
||||
- pass:
|
||||
when: "== rke2"
|
||||
message: RKE2 is a supported distribution
|
||||
- pass:
|
||||
when: "== k3s"
|
||||
message: K3S is a supported distribution
|
||||
- warn:
|
||||
message: Unable to determine the distribution of Kubernetes
|
||||
- nodeResources:
|
||||
|
||||
@@ -3,52 +3,52 @@ module github.com/replicatedhq/troubleshoot
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.57.0 // indirect
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
|
||||
github.com/aws/aws-sdk-go v1.25.18 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
|
||||
github.com/containers/image/v5 v5.10.4
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/go-openapi/spec v0.19.4 // indirect
|
||||
github.com/go-redis/redis/v7 v7.2.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/manifoldco/promptui v0.3.2
|
||||
github.com/manifoldco/promptui v0.8.0
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
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.21.1+incompatible
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tj/go-spin v1.1.0
|
||||
github.com/ulikunitz/xz v0.5.6 // indirect
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff // indirect
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.18.3
|
||||
k8s.io/apiextensions-apiserver v0.18.2
|
||||
k8s.io/apimachinery v0.18.3
|
||||
k8s.io/cli-runtime v0.18.0
|
||||
k8s.io/client-go v0.18.2
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
|
||||
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/api v0.20.2
|
||||
k8s.io/apiextensions-apiserver v0.20.2
|
||||
k8s.io/apimachinery v0.20.2
|
||||
k8s.io/cli-runtime v0.20.2
|
||||
k8s.io/client-go v0.20.2
|
||||
sigs.k8s.io/controller-runtime v0.8.3
|
||||
)
|
||||
|
||||
+45
-36
@@ -1,6 +1,7 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -40,44 +41,37 @@ 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
|
||||
func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) []*AnalyzeResult {
|
||||
analyzer, ok := GetHostAnalyzer(hostAnalyzer)
|
||||
if !ok {
|
||||
return NewAnalyzeResultError(analyzer, errors.New("invalid analyzer"))
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
isExcluded, _ := analyzer.IsExcluded()
|
||||
if isExcluded {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := analyzer.Analyze(getFile)
|
||||
if err != nil {
|
||||
return NewAnalyzeResultError(analyzer, errors.Wrap(err, "analyze"))
|
||||
}
|
||||
return []*AnalyzeResult{result}
|
||||
}
|
||||
|
||||
func NewAnalyzeResultError(analyzer HostAnalyzer, err error) []*AnalyzeResult {
|
||||
if analyzer != nil {
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: analyzer.Title(),
|
||||
Message: fmt.Sprintf("Analyzer Failed: %v", err),
|
||||
}}
|
||||
}
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: "nil analyzer",
|
||||
Message: fmt.Sprintf("Analyzer Failed: %v", err),
|
||||
}}
|
||||
}
|
||||
|
||||
func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
@@ -306,5 +300,20 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
if analyzer.RegistryImages != nil {
|
||||
isExcluded, err := isExcluded(analyzer.RegistryImages.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
result, err := analyzeRegistry(analyzer.RegistryImages, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type providers struct {
|
||||
aks bool
|
||||
ibm bool
|
||||
minikube bool
|
||||
rke2 bool
|
||||
k3s bool
|
||||
}
|
||||
|
||||
type Provider int
|
||||
@@ -38,6 +40,8 @@ const (
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
minikube Provider = iota
|
||||
rke2 Provider = iota
|
||||
k3s Provider = iota
|
||||
)
|
||||
|
||||
func CheckOpenShift(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
|
||||
@@ -77,6 +81,21 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders.minikube = true
|
||||
stringProvider = "minikube"
|
||||
}
|
||||
if k == "node.kubernetes.io/instance-type" && v == "k3s" {
|
||||
foundProviders.k3s = true
|
||||
stringProvider = "k3s"
|
||||
}
|
||||
if k == "beta.kubernetes.io/instance-type" && v == "k3s" {
|
||||
foundProviders.k3s = true
|
||||
stringProvider = "k3s"
|
||||
}
|
||||
}
|
||||
|
||||
for k := range node.ObjectMeta.Annotations {
|
||||
if k == "rke2.io/node-args" {
|
||||
foundProviders.rke2 = true
|
||||
stringProvider = "rke2"
|
||||
}
|
||||
}
|
||||
|
||||
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
|
||||
@@ -270,6 +289,10 @@ func compareDistributionConditionalToActual(conditional string, actual providers
|
||||
isMatch = actual.ibm
|
||||
case minikube:
|
||||
isMatch = actual.minikube
|
||||
case rke2:
|
||||
isMatch = actual.rke2
|
||||
case k3s:
|
||||
isMatch = actual.k3s
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
@@ -304,6 +327,10 @@ func mustNormalizeDistributionName(raw string) Provider {
|
||||
return ibm
|
||||
case "minikube":
|
||||
return minikube
|
||||
case "rke2":
|
||||
return rke2
|
||||
case "k3s":
|
||||
return k3s
|
||||
}
|
||||
|
||||
return unknown
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package analyzer
|
||||
|
||||
import troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
|
||||
type HostAnalyzer interface {
|
||||
Title() string
|
||||
IsExcluded() (bool, error)
|
||||
Analyze(getFile func(string) ([]byte, error)) (*AnalyzeResult, error)
|
||||
}
|
||||
|
||||
func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, bool) {
|
||||
switch {
|
||||
case analyzer.CPU != nil:
|
||||
return &AnalyzeHostCPU{analyzer.CPU}, true
|
||||
case analyzer.Memory != nil:
|
||||
return &AnalyzeHostMemory{analyzer.Memory}, true
|
||||
case analyzer.TCPLoadBalancer != nil:
|
||||
return &AnalyzeHostTCPLoadBalancer{analyzer.TCPLoadBalancer}, true
|
||||
case analyzer.HTTPLoadBalancer != nil:
|
||||
return &AnalyzeHostHTTPLoadBalancer{analyzer.HTTPLoadBalancer}, true
|
||||
case analyzer.DiskUsage != nil:
|
||||
return &AnalyzeHostDiskUsage{analyzer.DiskUsage}, true
|
||||
case analyzer.TCPPortStatus != nil:
|
||||
return &AnalyzeHostTCPPortStatus{analyzer.TCPPortStatus}, true
|
||||
case analyzer.HTTP != nil:
|
||||
return &AnalyzeHostHTTP{analyzer.HTTP}, true
|
||||
case analyzer.Time != nil:
|
||||
return &AnalyzeHostTime{analyzer.Time}, true
|
||||
case analyzer.BlockDevices != nil:
|
||||
return &AnalyzeHostBlockDevices{analyzer.BlockDevices}, true
|
||||
case analyzer.TCPConnect != nil:
|
||||
return &AnalyzeHostTCPConnect{analyzer.TCPConnect}, true
|
||||
case analyzer.IPV4Interfaces != nil:
|
||||
return &AnalyzeHostIPV4Interfaces{analyzer.IPV4Interfaces}, true
|
||||
case analyzer.FilesystemPerformance != nil:
|
||||
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
|
||||
case analyzer.Certificate != nil:
|
||||
return &AnalyzeHostCertificate{analyzer.Certificate}, true
|
||||
case analyzer.HostServices != nil:
|
||||
return &AnalyzeHostServices{analyzer.HostServices}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func hostAnalyzerTitleOrDefault(meta troubleshootv1beta2.AnalyzeMeta, defaultTitle string) string {
|
||||
if meta.CheckName != "" {
|
||||
return meta.CheckName
|
||||
}
|
||||
return defaultTitle
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type AnalyzeHostBlockDevices struct {
|
||||
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Block Devices")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents("system/block_devices.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var devices []collect.BlockDeviceInfo
|
||||
if err := json.Unmarshal(contents, &devices); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal block devices info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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 := compareHostBlockDevicesConditionalToActual(outcome.Fail.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
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 := compareHostBlockDevicesConditionalToActual(outcome.Warn.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
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 := compareHostBlockDevicesConditionalToActual(outcome.Pass.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
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
|
||||
}
|
||||
|
||||
// <regexp> <op> <count>
|
||||
// example: sdb > 0
|
||||
func compareHostBlockDevicesConditionalToActual(conditional string, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
rx, err := regexp.Compile(parts[0])
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to compile regex %q", parts[0])
|
||||
}
|
||||
count := countEligibleBlockDevices(rx, minimumAcceptableSize, includeUnmountedPartitions, devices)
|
||||
|
||||
desiredInt, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse desired quantity %q", parts[2])
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case ">":
|
||||
return count > desiredInt, nil
|
||||
case ">=":
|
||||
return count >= desiredInt, nil
|
||||
case "<":
|
||||
return count < desiredInt, nil
|
||||
case "<=":
|
||||
return count <= desiredInt, nil
|
||||
case "=", "==", "===":
|
||||
return count == desiredInt, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unexpected operator %q", parts[1])
|
||||
}
|
||||
|
||||
func countEligibleBlockDevices(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) int {
|
||||
count := 0
|
||||
|
||||
for _, device := range devices {
|
||||
if isEligibleBlockDevice(rx, minimumAcceptableSize, includeUnmountedPartitions, device, devices) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func isEligibleBlockDevice(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
|
||||
if !rx.MatchString(device.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if includeUnmountedPartitions {
|
||||
if device.Type != "disk" && device.Type != "part" {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if device.Type != "disk" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if minimumAcceptableSize != 0 {
|
||||
if device.Size < minimumAcceptableSize {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if device.Mountpoint != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.FilesystemType != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.ReadOnly {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.Removable {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, d := range devices {
|
||||
if d.ParentKernelName == device.KernelName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
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 TestAnalyzeBlockDevices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
devices []collect.BlockDeviceInfo
|
||||
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "sdb == 1, pass when there is an empty /dev/sdb",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sdb == 1, fail when partitioned",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sdb == 1, fail when it has a filesystem",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
FilesystemType: "ext4",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* > 0, fail when only loop devices are found",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "loop0",
|
||||
KernelName: "loop0",
|
||||
Type: "loop",
|
||||
Major: 7,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* > 0",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* > 1, pass with unmounted partition",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
IncludeUnmountedPartitions: true,
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* >= 1",
|
||||
Message: "Block device or partition available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Block device or partition available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* = 2, pass with two unmounted partitions/devices of at least 10gb in size",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
Size: 1024 * 1024 * 1024 * 128,
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
Size: 1024 * 1024 * 1024 * 128,
|
||||
},
|
||||
{
|
||||
Name: "sdc",
|
||||
KernelName: "sdc",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk2",
|
||||
Size: 1024 * 1024 * 1024 * 16,
|
||||
},
|
||||
{
|
||||
Name: "sdd",
|
||||
KernelName: "sdd",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk3",
|
||||
Size: 1024 * 1024 * 1024 * 8,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
IncludeUnmountedPartitions: true,
|
||||
MinimumAcceptableSize: 1024 * 1024 * 1024 * 10,
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* = 2",
|
||||
Message: "Two block devices or partitions of >10gb size available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Two block devices or partitions of >10gb size available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.devices)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostBlockDevices{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type AnalyzeHostCertificate struct {
|
||||
hostAnalyzer *troubleshootv1beta2.CertificateAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificate) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Certificate Key Pair")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificate) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCertificate) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "certificate"
|
||||
}
|
||||
name := filepath.Join("certificate", collectorName+".json")
|
||||
contents, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
status := string(contents)
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" || outcome.Fail.When == status {
|
||||
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 == "" || outcome.Warn.When == status {
|
||||
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 == "" || outcome.Pass.When == status {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"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 TestAnalyzeCertificate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
status string
|
||||
hostAnalyzer *troubleshootv1beta2.CertificateAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "key-pair-valid",
|
||||
status: collect.KeyPairValid,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsPass: true,
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key-pair-invalid",
|
||||
status: collect.KeyPairInvalid,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsFail: true,
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key-pair-missing",
|
||||
status: collect.KeyPairMissing,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsFail: true,
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key-pair-switched",
|
||||
status: collect.KeyPairSwitched,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsFail: true,
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key-pair-encrypted",
|
||||
status: collect.KeyPairEncrypted,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsFail: true,
|
||||
Message: "Private key is encrypted",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key-pair-mismatch",
|
||||
status: collect.KeyPairMismatch,
|
||||
hostAnalyzer: &troubleshootv1beta2.CertificateAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-missing",
|
||||
Message: "Certificate key pair not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-switched",
|
||||
Message: "Public and private keys switched",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-encrypted",
|
||||
Message: "Private key is mismatch",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-mismatch",
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-invalid",
|
||||
Message: "Certificate key pair is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "key-pair-valid",
|
||||
Message: "Certificate key pair is valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Certificate Key Pair",
|
||||
IsFail: true,
|
||||
Message: "Public and private keys don't match",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return []byte(test.status), nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostCertificate{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
+16
-6
@@ -10,7 +10,21 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostCPU(hostAnalyzer *troubleshootv1beta2.CPUAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
type AnalyzeHostCPU struct {
|
||||
hostAnalyzer *troubleshootv1beta2.CPUAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCPU) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Number of CPUs")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCPU) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostCPU) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents("system/cpu.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
@@ -23,11 +37,7 @@ func analyzeHostCPU(hostAnalyzer *troubleshootv1beta2.CPUAnalyze, getCollectedFi
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Number of CPUs"
|
||||
}
|
||||
result.Title = title
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -12,7 +12,21 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func analyzeHostDiskUsage(hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
type AnalyzeHostDiskUsage struct {
|
||||
hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostDiskUsage) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, fmt.Sprintf("Disk Usage %s", a.hostAnalyzer.CollectorName))
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostDiskUsage) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostDiskUsage) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
key := collect.HostDiskUsageKey(hostAnalyzer.CollectorName)
|
||||
contents, err := getCollectedFileContents(key)
|
||||
if err != nil {
|
||||
@@ -26,11 +40,7 @@ func analyzeHostDiskUsage(hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze, ge
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = fmt.Sprintf("Disk Usage %s", hostAnalyzer.CollectorName)
|
||||
}
|
||||
result.Title = title
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -367,7 +367,7 @@ func TestAnalyzeHostDiskUsage(t *testing.T) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostDiskUsage(test.hostAnalyzer, getCollectedFileContents)
|
||||
result, err := (&AnalyzeHostDiskUsage{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type AnalyzeHostFilesystemPerformance struct {
|
||||
hostAnalyzer *troubleshootv1beta2.FilesystemPerformanceAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostFilesystemPerformance) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Filesystem Performance")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostFilesystemPerformance) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostFilesystemPerformance) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "filesystemPerformance"
|
||||
}
|
||||
name := filepath.Join("filesystemPerformance", collectorName+".json")
|
||||
contents, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get collected file %s", name)
|
||||
}
|
||||
|
||||
fsPerf := collect.FSPerfResults{}
|
||||
if err := json.Unmarshal(contents, &fsPerf); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal filesystem performance results from %s", name)
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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 := compareHostFilesystemPerformanceConditionalToActual(outcome.Fail.When, fsPerf)
|
||||
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 := compareHostFilesystemPerformanceConditionalToActual(outcome.Warn.When, fsPerf)
|
||||
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 := compareHostFilesystemPerformanceConditionalToActual(outcome.Pass.When, fsPerf)
|
||||
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 compareHostFilesystemPerformanceConditionalToActual(conditional string, fsPerf collect.FSPerfResults) (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))
|
||||
}
|
||||
keyword := strings.ToLower(parts[0])
|
||||
comparator := parts[1]
|
||||
|
||||
if keyword == "iops" {
|
||||
desiredInt, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse desired iops %q as integer", parts[2])
|
||||
}
|
||||
return doCompareHostFilesystemIOPS(comparator, fsPerf.IOPS, int(desiredInt))
|
||||
}
|
||||
|
||||
desiredDuration, err := time.ParseDuration(parts[2])
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse duration %q", parts[2])
|
||||
}
|
||||
|
||||
switch keyword {
|
||||
case "min":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.Min, desiredDuration)
|
||||
case "max":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.Max, desiredDuration)
|
||||
case "average":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.Average, desiredDuration)
|
||||
case "p1":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P1, desiredDuration)
|
||||
case "p5":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P5, desiredDuration)
|
||||
case "p10":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P10, desiredDuration)
|
||||
case "p20":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P20, desiredDuration)
|
||||
case "p30":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P30, desiredDuration)
|
||||
case "p40":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P40, desiredDuration)
|
||||
case "p50":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P50, desiredDuration)
|
||||
case "p60":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P60, desiredDuration)
|
||||
case "p70":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P70, desiredDuration)
|
||||
case "p80":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P80, desiredDuration)
|
||||
case "p90":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P90, desiredDuration)
|
||||
case "p95":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P95, desiredDuration)
|
||||
case "p99":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P99, desiredDuration)
|
||||
case "p995":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P995, desiredDuration)
|
||||
case "p999":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P999, desiredDuration)
|
||||
case "p9995":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P9995, desiredDuration)
|
||||
case "p9999":
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P9999, desiredDuration)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown filesystem performance keyword %q", keyword)
|
||||
}
|
||||
|
||||
func doCompareHostFilesystemPerformance(operator string, actual time.Duration, desired time.Duration) (bool, error) {
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < desired, nil
|
||||
case "<=":
|
||||
return actual <= desired, nil
|
||||
case ">":
|
||||
return actual > desired, nil
|
||||
case ">=":
|
||||
return actual >= desired, nil
|
||||
case "=", "==", "===":
|
||||
return actual == desired, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown filesystem performance operator %q", operator)
|
||||
}
|
||||
|
||||
func doCompareHostFilesystemIOPS(operator string, actual int, desired int) (bool, error) {
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < desired, nil
|
||||
case "<=":
|
||||
return actual <= desired, nil
|
||||
case ">":
|
||||
return actual > desired, nil
|
||||
case ">=":
|
||||
return actual >= desired, nil
|
||||
case "=", "==", "===":
|
||||
return actual == desired, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown filesystem IOPS operator %q", operator)
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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 TestAnalyzeHostFilesystemPerformance(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fsPerf *collect.FSPerfResults
|
||||
hostAnalyzer *troubleshootv1beta2.FilesystemPerformanceAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "IOPS",
|
||||
fsPerf: &collect.FSPerfResults{
|
||||
IOPS: 50,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "iops < 20",
|
||||
Message: "IOPS < 20",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "iops <= 20",
|
||||
Message: "IOPS <= 30",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "iops > 70",
|
||||
Message: "IOPS > 70",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "iops >= 100",
|
||||
Message: "IOPS >= 100",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "iops == 50",
|
||||
Message: "IOPS == 50",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Filesystem Performance",
|
||||
IsPass: true,
|
||||
Message: "IOPS == 50",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cover",
|
||||
fsPerf: &collect.FSPerfResults{
|
||||
Min: 200 * time.Nanosecond,
|
||||
Max: time.Second,
|
||||
Average: 55 * time.Microsecond,
|
||||
P1: 1 * time.Microsecond,
|
||||
P5: 5 * time.Microsecond,
|
||||
P10: 10 * time.Microsecond,
|
||||
P20: 20 * time.Microsecond,
|
||||
P30: 30 * time.Microsecond,
|
||||
P40: 40 * time.Microsecond,
|
||||
P50: 50 * time.Microsecond,
|
||||
P60: 60 * time.Microsecond,
|
||||
P70: 70 * time.Microsecond,
|
||||
P80: 80 * time.Microsecond,
|
||||
P90: 90 * time.Microsecond,
|
||||
P95: 95 * time.Microsecond,
|
||||
P99: 99 * time.Microsecond,
|
||||
P995: 995 * time.Microsecond,
|
||||
P999: 999 * time.Microsecond,
|
||||
P9995: 5 * time.Millisecond,
|
||||
P9999: 9 * time.Millisecond,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "etcd",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min == 0",
|
||||
Message: "min not working",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min <= 50ns",
|
||||
Message: "lte operator not working",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "max == 0",
|
||||
Message: "max not working",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "max >= 1m",
|
||||
Message: "gte operator not working",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "average == 0",
|
||||
Message: "average not working",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p1 < 1us",
|
||||
Message: "P1 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p1 > 1us",
|
||||
Message: "P1 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p5 < 5us",
|
||||
Message: "P5 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p5 > 5us",
|
||||
Message: "P5 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p10 < 10us",
|
||||
Message: "P10 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p10 > 10us",
|
||||
Message: "P10 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p20 < 20us",
|
||||
Message: "P20 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p20 > 20us",
|
||||
Message: "P20 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p30 < 30us",
|
||||
Message: "P30 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p30 > 30us",
|
||||
Message: "P30 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p40 < 40us",
|
||||
Message: "P40 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p40 > 40us",
|
||||
Message: "P40 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p50 < 50us",
|
||||
Message: "P50 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p50 > 50us",
|
||||
Message: "P50 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p60 < 60us",
|
||||
Message: "P60 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p60 > 60us",
|
||||
Message: "P60 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p70 < 70us",
|
||||
Message: "P70 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p70 > 70us",
|
||||
Message: "P70 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p80 < 80us",
|
||||
Message: "P80 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p80 > 80us",
|
||||
Message: "P80 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p90 < 90us",
|
||||
Message: "P90 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p90 > 90us",
|
||||
Message: "P90 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p95 < 95us",
|
||||
Message: "P95 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p95 > 95us",
|
||||
Message: "P95 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p99 < 99us",
|
||||
Message: "P99 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p99 > 99us",
|
||||
Message: "P99 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p995 < 995us",
|
||||
Message: "P995 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p995 > 995us",
|
||||
Message: "P995 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p999 < 999us",
|
||||
Message: "P999 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p999 > 999us",
|
||||
Message: "P999 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p9995 < 5ms",
|
||||
Message: "P9995 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p9995 > 5ms",
|
||||
Message: "P9995 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p9999 < 9ms",
|
||||
Message: "P9999 too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p9999 > 9ms",
|
||||
Message: "P9999 too high",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p9999 < 10ms",
|
||||
Message: "Acceptable write latency",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Filesystem Performance",
|
||||
IsPass: true,
|
||||
Message: "Acceptable write latency",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.fsPerf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostFilesystemPerformance{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type httpResult struct {
|
||||
Error *collect.HTTPError
|
||||
Response *collect.HTTPResponse
|
||||
}
|
||||
|
||||
type AnalyzeHostHTTP struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HTTPAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTP) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "HTTP Request")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTP) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTP) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
name := filepath.Join("http", "result.json")
|
||||
if hostAnalyzer.CollectorName != "" {
|
||||
name = filepath.Join("http", hostAnalyzer.CollectorName+".json")
|
||||
}
|
||||
contents, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
httpInfo := &httpResult{}
|
||||
if err := json.Unmarshal(contents, httpInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal http result")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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 := compareHostHTTPConditionalToActual(outcome.Fail.When, httpInfo)
|
||||
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 := compareHostHTTPConditionalToActual(outcome.Warn.When, httpInfo)
|
||||
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 := compareHostHTTPConditionalToActual(outcome.Pass.When, httpInfo)
|
||||
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 compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) {
|
||||
if conditional == "error" {
|
||||
return result.Error != nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts))
|
||||
}
|
||||
|
||||
if parts[0] != "statusCode" {
|
||||
return false, errors.New(`Conditional must begin with keyword "statusCode"`)
|
||||
}
|
||||
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result.Response == nil {
|
||||
return false, err
|
||||
}
|
||||
return result.Response.Status == i, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
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 TestAnalyzeHostHTTP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
httpResult *httpResult
|
||||
hostAnalyzer *troubleshootv1beta2.HTTPAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
httpResult: &httpResult{
|
||||
Error: &collect.HTTPError{
|
||||
Message: "i/o timeout",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
|
||||
CollectorName: "registry",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "error",
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "HTTP Request",
|
||||
IsFail: true,
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "200",
|
||||
httpResult: &httpResult{
|
||||
Response: &collect.HTTPResponse{
|
||||
Status: 200,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
|
||||
CollectorName: "registry",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "error",
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "statusCode == 204",
|
||||
Message: "No content",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "statusCode == 200",
|
||||
Message: "Successfully reached registry",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "HTTP Request",
|
||||
IsPass: true,
|
||||
Message: "Successfully reached registry",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.httpResult)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostHTTP{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type AnalyzeHostHTTPLoadBalancer struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HTTPLoadBalancerAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTPLoadBalancer) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "HTTP Load Balancer")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTPLoadBalancer) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostHTTPLoadBalancer) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "httpLoadBalancer"
|
||||
}
|
||||
fullPath := path.Join("httpLoadBalancer", 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{}
|
||||
|
||||
result.Title = a.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
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type AnalyzeHostIPV4Interfaces struct {
|
||||
hostAnalyzer *troubleshootv1beta2.IPV4InterfacesAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostIPV4Interfaces) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "IPv4 Interfaces")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostIPV4Interfaces) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostIPV4Interfaces) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents("system/ipv4Interfaces.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var ipv4Interfaces []net.Interface
|
||||
if err := json.Unmarshal(contents, &ipv4Interfaces); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal ipv4Interfaces")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Fail.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Warn.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostIPV4InterfacesConditionalToActual(outcome.Pass.When, ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostIPV4InterfacesConditionalToActual(conditional string, ipv4Interfaces []net.Interface) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts in conditional, got %d", len(parts))
|
||||
}
|
||||
|
||||
keyword := parts[0]
|
||||
operator := parts[1]
|
||||
desired := parts[2]
|
||||
|
||||
if keyword != "count" {
|
||||
return false, fmt.Errorf(`Only supported keyword is "count", got %q`, keyword)
|
||||
}
|
||||
|
||||
desiredInt, err := strconv.ParseInt(desired, 10, 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse %q as int", desired)
|
||||
}
|
||||
|
||||
actualCount := len(ipv4Interfaces)
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actualCount < int(desiredInt), nil
|
||||
case "<=":
|
||||
return actualCount <= int(desiredInt), nil
|
||||
case ">":
|
||||
return actualCount > int(desiredInt), nil
|
||||
case ">=":
|
||||
return actualCount >= int(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return actualCount == int(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown operator %q. Supported operators are: <, <=, ==, >=, >", operator)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeIPV4Interfaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
interfaces []net.Interface
|
||||
hostAnalyzer *troubleshootv1beta2.IPV4InterfacesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "fail when no ipv4 interfaces detected",
|
||||
interfaces: nil,
|
||||
hostAnalyzer: &troubleshootv1beta2.IPV4InterfacesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count > 0",
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count == 0",
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "IPv4 Interfaces",
|
||||
IsFail: true,
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass when ipv4 interfaces detected",
|
||||
interfaces: []net.Interface{
|
||||
{
|
||||
Index: 1,
|
||||
MTU: 1460,
|
||||
HardwareAddr: net.HardwareAddr("42010a80001d"),
|
||||
Name: "ens4",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.IPV4InterfacesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count == 0",
|
||||
Message: "No IPv4 interfaces detected",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "count > 0",
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "IPv4 Interfaces",
|
||||
IsPass: true,
|
||||
Message: "IPv4 interface available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.interfaces)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostIPV4Interfaces{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,21 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func analyzeHostMemory(hostAnalyzer *troubleshootv1beta2.MemoryAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
type AnalyzeHostMemory struct {
|
||||
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostMemory) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Amount of Memory")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostMemory) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostMemory) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents("system/memory.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
@@ -24,11 +38,7 @@ func analyzeHostMemory(hostAnalyzer *troubleshootv1beta2.MemoryAnalyze, getColle
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Amount of Memory"
|
||||
}
|
||||
result.Title = title
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestAnalyzeHostMemory(t *testing.T) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostMemory(test.hostAnalyzer, getCollectedFileContents)
|
||||
result, err := (&AnalyzeHostMemory{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type AnalyzeHostServices struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HostServicesAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Host Services")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents(collect.HostServicesPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var services []collect.ServiceInfo
|
||||
if err := json.Unmarshal(contents, &services); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal systemctl service info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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 := compareHostServicesConditionalToActual(outcome.Fail.When, services)
|
||||
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 := compareHostServicesConditionalToActual(outcome.Warn.When, services)
|
||||
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 := compareHostServicesConditionalToActual(outcome.Pass.When, services)
|
||||
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
|
||||
}
|
||||
|
||||
// <service> <op> <state>
|
||||
// example: ufw.service = active
|
||||
func compareHostServicesConditionalToActual(conditional string, services []collect.ServiceInfo) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
matchParams := strings.Split(parts[2], ",")
|
||||
activeMatch := matchParams[0]
|
||||
subMatch := ""
|
||||
loadMatch := ""
|
||||
if len(matchParams) > 1 {
|
||||
subMatch = matchParams[1]
|
||||
}
|
||||
if len(matchParams) > 2 {
|
||||
loadMatch = matchParams[2]
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "=", "==":
|
||||
for _, service := range services {
|
||||
if isServiceMatch(service.Unit, parts[0]) {
|
||||
isMatch := true
|
||||
if activeMatch != "" && activeMatch != "*" {
|
||||
isMatch = isMatch && (activeMatch == service.Active)
|
||||
}
|
||||
if subMatch != "" && subMatch != "*" {
|
||||
isMatch = isMatch && (subMatch == service.Sub)
|
||||
}
|
||||
if loadMatch != "" && loadMatch != "*" {
|
||||
isMatch = isMatch && (loadMatch == service.Load)
|
||||
}
|
||||
|
||||
return isMatch, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
case "!=", "<>":
|
||||
for _, service := range services {
|
||||
if isServiceMatch(service.Unit, parts[0]) {
|
||||
isMatch := false
|
||||
if activeMatch != "" && activeMatch != "*" {
|
||||
isMatch = isMatch || (activeMatch != service.Active)
|
||||
}
|
||||
if subMatch != "" && subMatch != "*" {
|
||||
isMatch = isMatch || (subMatch != service.Sub)
|
||||
}
|
||||
if loadMatch != "" && loadMatch != "*" {
|
||||
isMatch = isMatch || (loadMatch != service.Load)
|
||||
}
|
||||
|
||||
return isMatch, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("unexpected operator %q", parts[1])
|
||||
}
|
||||
|
||||
func isServiceMatch(serviceName string, matchName string) bool {
|
||||
if serviceName == matchName {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(serviceName, matchName) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
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 TestAnalyzeHostServices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
info []collect.ServiceInfo
|
||||
hostAnalyzer *troubleshootv1beta2.HostServicesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "service 'a' is active",
|
||||
info: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "a.service",
|
||||
Active: "active",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostServicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "a.service == active",
|
||||
Message: "the service 'a' is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Host Services",
|
||||
IsFail: true,
|
||||
Message: "the service 'a' is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "connected, fail",
|
||||
info: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "a.service",
|
||||
Active: "active",
|
||||
},
|
||||
{
|
||||
Unit: "b.service",
|
||||
Active: "inactive",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostServicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "a.service != active",
|
||||
Message: "service 'a' is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "b.service != active",
|
||||
Message: "service 'b' is not active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Host Services",
|
||||
IsPass: true,
|
||||
Message: "service 'b' is not active",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostServices{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_compareHostServicesConditionalToActual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
services []collect.ServiceInfo
|
||||
wantRes bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "match second item",
|
||||
conditional: "abc.service = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "first",
|
||||
},
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "running",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
{
|
||||
name: "item not in list",
|
||||
conditional: "abc = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "first",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "item does not match",
|
||||
conditional: "abc = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "inactive",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "other operator",
|
||||
conditional: "abc * active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "inactive",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "item active matches but not sub",
|
||||
conditional: "abc = active,running",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "item active,sub,load matches",
|
||||
conditional: "abc = active,*,loaded",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
Load: "loaded",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
{
|
||||
name: "one item active,sub,load does not match with !=",
|
||||
conditional: "abc != active,running,loaded",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
Load: "loaded",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
gotRes, err := compareHostServicesConditionalToActual(tt.conditional, tt.services)
|
||||
if tt.wantErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
req.Equal(tt.wantRes, gotRes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type AnalyzeHostTCPConnect struct {
|
||||
hostAnalyzer *troubleshootv1beta2.TCPConnectAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPConnect) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TCP Connection Attempt")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPConnect) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPConnect) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
fullPath := path.Join("connect", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
|
||||
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
|
||||
}
|
||||
actual := collect.NetworkStatusResult{}
|
||||
if err := json.Unmarshal(collected, &actual); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal collected")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeTCPConnect(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
info *collect.NetworkStatusResult
|
||||
hostAnalyzer *troubleshootv1beta2.TCPConnectAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "connection refused, fail",
|
||||
info: &collect.NetworkStatusResult{
|
||||
Status: collect.NetworkStatusConnectionRefused,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TCPConnectAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "connection-refused",
|
||||
Message: "Connection was refused",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "TCP Connection Attempt",
|
||||
IsFail: true,
|
||||
Message: "Connection was refused",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "connected, fail",
|
||||
info: &collect.NetworkStatusResult{
|
||||
Status: collect.NetworkStatusConnected,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TCPConnectAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "connection-refused",
|
||||
Message: "Connection was refused",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "connected",
|
||||
Message: "Connection was successful",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "TCP Connection Attempt",
|
||||
IsPass: true,
|
||||
Message: "Connection was successful",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostTCPConnect{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,21 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostTCPLoadBalancer(hostAnalyzer *troubleshootv1beta2.TCPLoadBalancerAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
type AnalyzeHostTCPLoadBalancer struct {
|
||||
hostAnalyzer *troubleshootv1beta2.TCPLoadBalancerAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPLoadBalancer) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TCP Load Balancer")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPLoadBalancer) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPLoadBalancer) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "tcpLoadBalancer"
|
||||
@@ -29,11 +43,7 @@ func analyzeHostTCPLoadBalancer(hostAnalyzer *troubleshootv1beta2.TCPLoadBalance
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "TCP Load Balancer"
|
||||
}
|
||||
result.Title = title
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -10,7 +10,21 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostTCPPortStatus(hostAnalyzer *troubleshootv1beta2.TCPPortStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
type AnalyzeHostTCPPortStatus struct {
|
||||
hostAnalyzer *troubleshootv1beta2.TCPPortStatusAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPPortStatus) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "TCP Port Status")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPPortStatus) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTCPPortStatus) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
fullPath := path.Join("tcpPortStatus", "tcpPortStatus.json")
|
||||
if hostAnalyzer.CollectorName != "" {
|
||||
fullPath = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
|
||||
@@ -27,11 +41,7 @@ func analyzeHostTCPPortStatus(hostAnalyzer *troubleshootv1beta2.TCPPortStatusAna
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "TCP Port Status"
|
||||
}
|
||||
result.Title = title
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
SynchronizedActive = "synchronized+active"
|
||||
SynchronizedInactive = "synchronized+inactive"
|
||||
UnsynchronizedActive = "unsynchronized+active"
|
||||
UnsynchronizedInactive = "unsynchronized+inactive"
|
||||
)
|
||||
|
||||
type AnalyzeHostTime struct {
|
||||
hostAnalyzer *troubleshootv1beta2.TimeAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTime) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Time")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTime) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostTime) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents("system/time.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
timeInfo := collect.TimeInfo{}
|
||||
if err := json.Unmarshal(contents, &timeInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal time info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.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 := compareHostTimeStatusToActual(outcome.Fail.When, timeInfo)
|
||||
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 := compareHostTimeStatusToActual(outcome.Warn.When, timeInfo)
|
||||
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 := compareHostTimeStatusToActual(outcome.Pass.When, timeInfo)
|
||||
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 compareHostTimeStatusToActual(status string, timeInfo collect.TimeInfo) (res bool, err error) {
|
||||
parts := strings.Split(status, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
if parts[0] == "timezone" {
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" && parts[1] != "!=" {
|
||||
return false, errors.New(`Only supported operators are "==" and "!="`)
|
||||
}
|
||||
if parts[1] == "!=" {
|
||||
return parts[2] != timeInfo.Timezone, nil
|
||||
}
|
||||
return parts[2] == timeInfo.Timezone, nil
|
||||
}
|
||||
|
||||
if parts[0] == "ntp" {
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case SynchronizedActive:
|
||||
return timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
|
||||
case SynchronizedInactive:
|
||||
return timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
|
||||
case UnsynchronizedActive:
|
||||
return !timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
|
||||
case UnsynchronizedInactive:
|
||||
return !timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
|
||||
default:
|
||||
return false, fmt.Errorf("Unknown status %q. Allowed values are %q, %q, %q, or %q", parts[2], SynchronizedActive, SynchronizedInactive, UnsynchronizedActive, UnsynchronizedInactive)
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown keyword: %s", parts[0])
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
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 TestAnalyzeHostTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeInfo *collect.TimeInfo
|
||||
hostAnalyzer *troubleshootv1beta2.TimeAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "ntp == synchronized+active",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsPass: true,
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == unsynchronized+inactive",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: false,
|
||||
NTPActive: false,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsFail: true,
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == unsynchronized+active",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: false,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+active",
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsWarn: true,
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == synchronized+inactive",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: false,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+active",
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+inactive",
|
||||
Message: "System clock synchronized for now",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsWarn: true,
|
||||
Message: "System clock synchronized for now",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timezone",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "timezone == UTC",
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "timezone not set to UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsPass: true,
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timezone is not UTC",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "PST",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "timezone != UTC",
|
||||
Message: "Timezone is not set to UTC",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsFail: true,
|
||||
Message: "Timezone is not set to UTC",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.timeInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostTime{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func analyzePostgres(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollected
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
@@ -62,7 +62,7 @@ func analyzeRedis(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFil
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeRegistry(analyzer *troubleshootv1beta2.RegistryImagesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "images"
|
||||
}
|
||||
|
||||
fullPath := path.Join("registry", 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)
|
||||
}
|
||||
|
||||
registryInfo := collect.RegistryInfo{}
|
||||
if err := json.Unmarshal(collected, ®istryInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal database connection result")
|
||||
}
|
||||
|
||||
numMissingImages := 0
|
||||
numVerifiedImages := 0
|
||||
numErrors := 0
|
||||
for _, image := range registryInfo.Images {
|
||||
if image.Error != "" {
|
||||
numErrors++
|
||||
} else if !image.Exists {
|
||||
numMissingImages++
|
||||
} else {
|
||||
numVerifiedImages++
|
||||
}
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: title,
|
||||
IconKey: "kubernetes_registry_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/registry-analyze.svg",
|
||||
}
|
||||
|
||||
for _, outcome := range analyzer.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 := compareRegistryConditionalToActual(outcome.Fail.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
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 := compareRegistryConditionalToActual(outcome.Warn.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
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 := compareRegistryConditionalToActual(outcome.Pass.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func compareRegistryConditionalToActual(conditional string, numVerifiedImages int, numMissingImages int, numErrors int) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return false, errors.Errorf("unable to parse conditional: %s", conditional)
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "verified":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numVerifiedImages)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of verified images")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
case "missing":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numMissingImages)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of missing images")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
case "errors":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numErrors)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of errors")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown term %q in conditional", parts[0])
|
||||
}
|
||||
|
||||
func doCompareRegistryImageCount(operator string, desired string, actual int) (bool, error) {
|
||||
desiredInt, err := strconv.Atoi(desired)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse")
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < desiredInt, nil
|
||||
case "<=":
|
||||
return actual <= desiredInt, nil
|
||||
case ">":
|
||||
return actual > desiredInt, nil
|
||||
case ">=":
|
||||
return actual >= desiredInt, nil
|
||||
case "=", "==", "===":
|
||||
return actual == desiredInt, nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown operator: %s", operator)
|
||||
}
|
||||
@@ -303,6 +303,74 @@ func Test_textAnalyze(t *testing.T) {
|
||||
"text-collector-2/cfile-3.txt": []byte("Yes it all succeeded"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "case insensitive failure case 1", // regexes are not case insensitive by default
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile-1.txt",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-1",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all SUCCEEDED"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "case insensitive success case 1",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile-1.txt",
|
||||
RegexPattern: "(?i)succeeded",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "pass",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all SUCCEEDED"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -148,7 +148,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Namespace",
|
||||
Resource: "namespaces",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -160,7 +160,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Node",
|
||||
Resource: "nodes",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -172,7 +172,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "apiextensions.k8s.io",
|
||||
Version: "",
|
||||
Resource: "CustomResourceDefinition",
|
||||
Resource: "customresourcedefinitions",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -184,7 +184,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "storage.k8s.io",
|
||||
Version: "",
|
||||
Resource: "StorageClasses",
|
||||
Resource: "storageclasses",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -197,7 +197,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Secret",
|
||||
Resource: "secrets",
|
||||
Subresource: "",
|
||||
Name: c.Secret.SecretName,
|
||||
},
|
||||
@@ -210,7 +210,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -222,7 +222,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
Name: "",
|
||||
},
|
||||
@@ -235,7 +235,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "create",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -248,7 +248,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -260,7 +260,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "exec",
|
||||
Name: "",
|
||||
},
|
||||
@@ -273,7 +273,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -285,7 +285,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "exec",
|
||||
Name: "",
|
||||
},
|
||||
|
||||
@@ -116,6 +116,12 @@ type CephStatusAnalyze struct {
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
}
|
||||
|
||||
type RegistryImagesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
CollectorName string `json:"collectorName" yaml:"collectorName"`
|
||||
}
|
||||
|
||||
type AnalyzeMeta struct {
|
||||
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
|
||||
Exclude multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
|
||||
@@ -138,4 +144,5 @@ type Analyze struct {
|
||||
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
|
||||
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
CephStatus *CephStatusAnalyze `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
|
||||
RegistryImages *RegistryImagesAnalyze `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
|
||||
}
|
||||
|
||||
@@ -139,6 +139,13 @@ type Ceph struct {
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryImages struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Images []string `json:"images" yaml:"images"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
ImagePullSecrets *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
|
||||
}
|
||||
|
||||
type Collect struct {
|
||||
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
|
||||
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
|
||||
@@ -154,6 +161,7 @@ type Collect struct {
|
||||
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
|
||||
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
|
||||
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
|
||||
@@ -168,7 +176,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Namespace",
|
||||
Resource: "namespaces",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -180,7 +188,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Node",
|
||||
Resource: "nodes",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -192,7 +200,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "apiextensions.k8s.io",
|
||||
Version: "",
|
||||
Resource: "CustomResourceDefinition",
|
||||
Resource: "customresourcedefinitions",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -204,7 +212,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "storage.k8s.io",
|
||||
Version: "",
|
||||
Resource: "StorageClasses",
|
||||
Resource: "storageclasses",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -217,7 +225,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Secret",
|
||||
Resource: "secrets",
|
||||
Subresource: "",
|
||||
Name: c.Secret.SecretName,
|
||||
},
|
||||
@@ -230,7 +238,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -242,7 +250,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "log",
|
||||
Name: "",
|
||||
},
|
||||
@@ -255,7 +263,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "create",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -268,7 +276,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -280,7 +288,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "exec",
|
||||
Name: "",
|
||||
},
|
||||
@@ -293,7 +301,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "list",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
@@ -305,7 +313,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "Pod",
|
||||
Resource: "pods",
|
||||
Subresource: "exec",
|
||||
Name: "",
|
||||
},
|
||||
@@ -313,6 +321,21 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
})
|
||||
} else if c.HTTP != nil {
|
||||
// NOOP
|
||||
} else if c.RegistryImages != nil &&
|
||||
c.RegistryImages.ImagePullSecrets != nil &&
|
||||
c.RegistryImages.ImagePullSecrets.Data == nil {
|
||||
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||
Namespace: pickNamespaceOrDefault(c.RegistryImages.Namespace, overrideNS),
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "secrets",
|
||||
Subresource: "",
|
||||
Name: c.RegistryImages.ImagePullSecrets.Name,
|
||||
},
|
||||
NonResourceAttributes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -361,6 +384,10 @@ func (c *Collect) GetName() string {
|
||||
collector = "ceph"
|
||||
name = c.Ceph.CollectorName
|
||||
}
|
||||
if c.RegistryImages != nil {
|
||||
collector = "registry-images"
|
||||
name = c.RegistryImages.CollectorName
|
||||
}
|
||||
|
||||
if collector == "" {
|
||||
return "<none>"
|
||||
|
||||
@@ -16,6 +16,12 @@ type TCPLoadBalancerAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HTTPLoadBalancerAnalyze 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"`
|
||||
@@ -28,14 +34,77 @@ type DiskUsageAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HTTPAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type TimeAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type BlockDevicesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
MinimumAcceptableSize uint64 `json:"minimumAcceptableSize" yaml:"minimumAcceptableSize"`
|
||||
IncludeUnmountedPartitions bool `json:"includeUnmountedPartitions" yaml:"includeUnmountedPartitions"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type TCPConnectAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type IPV4InterfacesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type FilesystemPerformanceAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type CertificateAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostServicesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
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"`
|
||||
TCPLoadBalancer *TCPLoadBalancerAnalyze `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
|
||||
HTTPLoadBalancer *HTTPLoadBalancerAnalyze `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,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"`
|
||||
|
||||
HTTP *HTTPAnalyze `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
|
||||
Time *TimeAnalyze `json:"time,omitempty" yaml:"time,omitempty"`
|
||||
|
||||
BlockDevices *BlockDevicesAnalyze `json:"blockDevices,omitempty" yaml:"blockDevices,omitempty"`
|
||||
|
||||
TCPConnect *TCPConnectAnalyze `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
|
||||
IPV4Interfaces *IPV4InterfacesAnalyze `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
|
||||
|
||||
FilesystemPerformance *FilesystemPerformanceAnalyze `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
|
||||
Certificate *CertificateAnalyze `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
|
||||
HostServices *HostServicesAnalyze `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
}
|
||||
|
||||
@@ -52,15 +52,62 @@ type DiskUsage struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type HostHTTP struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
Get *Get `json:"get,omitempty" yaml:"get,omitempty"`
|
||||
Post *Post `json:"post,omitempty" yaml:"post,omitempty"`
|
||||
Put *Put `json:"put,omitempty" yaml:"put,omitempty"`
|
||||
}
|
||||
|
||||
type HostTime struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
type HostBlockDevices struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
type TCPConnect struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
Address string `json:"address"`
|
||||
Timeout string `json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type FilesystemPerformance struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
OperationSizeBytes uint64 `json:"operationSize,omitempty"`
|
||||
Directory string `json:"directory,omitempty"`
|
||||
FileSize string `json:"fileSize,omitempty"`
|
||||
Sync bool `json:"sync,omitempty"`
|
||||
Datasync bool `json:"datasync,omitempty"`
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
CertificatePath string `json:"certificatePath" yaml:"certificatepath"`
|
||||
KeyPath string `json:"keyPath" yaml:"keyPath"`
|
||||
}
|
||||
|
||||
type HostServices struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
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"`
|
||||
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"`
|
||||
HTTP *HostHTTP `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
Time *HostTime `json:"time,omitempty" yaml:"time,omitempty"`
|
||||
BlockDevices *HostBlockDevices `json:"blockDevices,omitempty" yaml:"blockDevices,omitempty"`
|
||||
TCPConnect *TCPConnect `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
FilesystemPerformance *FilesystemPerformance `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
Certificate *Certificate `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
HostServices *HostServices `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HostCollect) GetName() string {
|
||||
|
||||
@@ -132,6 +132,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
|
||||
*out = new(CephStatusAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RegistryImages != nil {
|
||||
in, out := &in.RegistryImages, &out.RegistryImages
|
||||
*out = new(RegistryImagesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
|
||||
@@ -287,6 +292,33 @@ 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 *BlockDevicesAnalyze) DeepCopyInto(out *BlockDevicesAnalyze) {
|
||||
*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 BlockDevicesAnalyze.
|
||||
func (in *BlockDevicesAnalyze) DeepCopy() *BlockDevicesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BlockDevicesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
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
|
||||
@@ -373,6 +405,49 @@ func (in *CephStatusAnalyze) DeepCopy() *CephStatusAnalyze {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Certificate) DeepCopyInto(out *Certificate) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
|
||||
func (in *Certificate) DeepCopy() *Certificate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Certificate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CertificateAnalyze) DeepCopyInto(out *CertificateAnalyze) {
|
||||
*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 CertificateAnalyze.
|
||||
func (in *CertificateAnalyze) DeepCopy() *CertificateAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CertificateAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) {
|
||||
*out = *in
|
||||
@@ -505,6 +580,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(Ceph)
|
||||
**out = **in
|
||||
}
|
||||
if in.RegistryImages != nil {
|
||||
in, out := &in.RegistryImages, &out.RegistryImages
|
||||
*out = new(RegistryImages)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
|
||||
@@ -974,6 +1054,49 @@ func (in *FileSelector) DeepCopy() *FileSelector {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FilesystemPerformance) DeepCopyInto(out *FilesystemPerformance) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemPerformance.
|
||||
func (in *FilesystemPerformance) DeepCopy() *FilesystemPerformance {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FilesystemPerformance)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FilesystemPerformanceAnalyze) DeepCopyInto(out *FilesystemPerformanceAnalyze) {
|
||||
*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 FilesystemPerformanceAnalyze.
|
||||
func (in *FilesystemPerformanceAnalyze) DeepCopy() *FilesystemPerformanceAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FilesystemPerformanceAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Get) DeepCopyInto(out *Get) {
|
||||
*out = *in
|
||||
@@ -1027,6 +1150,33 @@ 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 *HTTPAnalyze) DeepCopyInto(out *HTTPAnalyze) {
|
||||
*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 HTTPAnalyze.
|
||||
func (in *HTTPAnalyze) DeepCopy() *HTTPAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HTTPAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
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
|
||||
@@ -1043,6 +1193,33 @@ func (in *HTTPLoadBalancer) DeepCopy() *HTTPLoadBalancer {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTPLoadBalancerAnalyze) DeepCopyInto(out *HTTPLoadBalancerAnalyze) {
|
||||
*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 HTTPLoadBalancerAnalyze.
|
||||
func (in *HTTPLoadBalancerAnalyze) DeepCopy() *HTTPLoadBalancerAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HTTPLoadBalancerAnalyze)
|
||||
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
|
||||
@@ -1056,6 +1233,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(TCPLoadBalancerAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HTTPLoadBalancer != nil {
|
||||
in, out := &in.HTTPLoadBalancer, &out.HTTPLoadBalancer
|
||||
*out = new(HTTPLoadBalancerAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.DiskUsage != nil {
|
||||
in, out := &in.DiskUsage, &out.DiskUsage
|
||||
*out = new(DiskUsageAnalyze)
|
||||
@@ -1071,6 +1253,46 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(TCPPortStatusAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HTTP != nil {
|
||||
in, out := &in.HTTP, &out.HTTP
|
||||
*out = new(HTTPAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Time != nil {
|
||||
in, out := &in.Time, &out.Time
|
||||
*out = new(TimeAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.BlockDevices != nil {
|
||||
in, out := &in.BlockDevices, &out.BlockDevices
|
||||
*out = new(BlockDevicesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TCPConnect != nil {
|
||||
in, out := &in.TCPConnect, &out.TCPConnect
|
||||
*out = new(TCPConnectAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IPV4Interfaces != nil {
|
||||
in, out := &in.IPV4Interfaces, &out.IPV4Interfaces
|
||||
*out = new(IPV4InterfacesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.FilesystemPerformance != nil {
|
||||
in, out := &in.FilesystemPerformance, &out.FilesystemPerformance
|
||||
*out = new(FilesystemPerformanceAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Certificate != nil {
|
||||
in, out := &in.Certificate, &out.Certificate
|
||||
*out = new(CertificateAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServicesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
|
||||
@@ -1083,6 +1305,22 @@ func (in *HostAnalyze) DeepCopy() *HostAnalyze {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostBlockDevices) DeepCopyInto(out *HostBlockDevices) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostBlockDevices.
|
||||
func (in *HostBlockDevices) DeepCopy() *HostBlockDevices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostBlockDevices)
|
||||
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
|
||||
@@ -1126,6 +1364,41 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
|
||||
*out = new(DiskUsage)
|
||||
**out = **in
|
||||
}
|
||||
if in.HTTP != nil {
|
||||
in, out := &in.HTTP, &out.HTTP
|
||||
*out = new(HostHTTP)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Time != nil {
|
||||
in, out := &in.Time, &out.Time
|
||||
*out = new(HostTime)
|
||||
**out = **in
|
||||
}
|
||||
if in.BlockDevices != nil {
|
||||
in, out := &in.BlockDevices, &out.BlockDevices
|
||||
*out = new(HostBlockDevices)
|
||||
**out = **in
|
||||
}
|
||||
if in.TCPConnect != nil {
|
||||
in, out := &in.TCPConnect, &out.TCPConnect
|
||||
*out = new(TCPConnect)
|
||||
**out = **in
|
||||
}
|
||||
if in.FilesystemPerformance != nil {
|
||||
in, out := &in.FilesystemPerformance, &out.FilesystemPerformance
|
||||
*out = new(FilesystemPerformance)
|
||||
**out = **in
|
||||
}
|
||||
if in.Certificate != nil {
|
||||
in, out := &in.Certificate, &out.Certificate
|
||||
*out = new(Certificate)
|
||||
**out = **in
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServices)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
|
||||
@@ -1154,6 +1427,37 @@ func (in *HostCollectorMeta) DeepCopy() *HostCollectorMeta {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostHTTP) DeepCopyInto(out *HostHTTP) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
if in.Get != nil {
|
||||
in, out := &in.Get, &out.Get
|
||||
*out = new(Get)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Post != nil {
|
||||
in, out := &in.Post, &out.Post
|
||||
*out = new(Post)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Put != nil {
|
||||
in, out := &in.Put, &out.Put
|
||||
*out = new(Put)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostHTTP.
|
||||
func (in *HostHTTP) DeepCopy() *HostHTTP {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostHTTP)
|
||||
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
|
||||
@@ -1265,6 +1569,65 @@ func (in *HostPreflightStatus) DeepCopy() *HostPreflightStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostServices) DeepCopyInto(out *HostServices) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostServices.
|
||||
func (in *HostServices) DeepCopy() *HostServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostServices)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostServicesAnalyze) DeepCopyInto(out *HostServicesAnalyze) {
|
||||
*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 HostServicesAnalyze.
|
||||
func (in *HostServicesAnalyze) DeepCopy() *HostServicesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostServicesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostTime) DeepCopyInto(out *HostTime) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostTime.
|
||||
func (in *HostTime) DeepCopy() *HostTime {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostTime)
|
||||
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
|
||||
@@ -1281,6 +1644,33 @@ func (in *IPV4Interfaces) DeepCopy() *IPV4Interfaces {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IPV4InterfacesAnalyze) DeepCopyInto(out *IPV4InterfacesAnalyze) {
|
||||
*out = *in
|
||||
out.AnalyzeMeta = in.AnalyzeMeta
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPV4InterfacesAnalyze.
|
||||
func (in *IPV4InterfacesAnalyze) DeepCopy() *IPV4InterfacesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IPV4InterfacesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImagePullSecret) DeepCopyInto(out *ImagePullSecret) {
|
||||
*out = *in
|
||||
@@ -1854,6 +2244,59 @@ func (in *Regex) DeepCopy() *Regex {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RegistryImages) DeepCopyInto(out *RegistryImages) {
|
||||
*out = *in
|
||||
out.CollectorMeta = in.CollectorMeta
|
||||
if in.Images != nil {
|
||||
in, out := &in.Images, &out.Images
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImagePullSecrets != nil {
|
||||
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
|
||||
*out = new(ImagePullSecrets)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryImages.
|
||||
func (in *RegistryImages) DeepCopy() *RegistryImages {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RegistryImages)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RegistryImagesAnalyze) DeepCopyInto(out *RegistryImagesAnalyze) {
|
||||
*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 RegistryImagesAnalyze.
|
||||
func (in *RegistryImagesAnalyze) DeepCopy() *RegistryImagesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RegistryImagesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Removals) DeepCopyInto(out *Removals) {
|
||||
*out = *in
|
||||
@@ -2168,6 +2611,49 @@ func (in *SupportBundleVersionSpec) DeepCopy() *SupportBundleVersionSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPConnect) DeepCopyInto(out *TCPConnect) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPConnect.
|
||||
func (in *TCPConnect) DeepCopy() *TCPConnect {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TCPConnect)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPConnectAnalyze) DeepCopyInto(out *TCPConnectAnalyze) {
|
||||
*out = *in
|
||||
out.AnalyzeMeta = in.AnalyzeMeta
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPConnectAnalyze.
|
||||
func (in *TCPConnectAnalyze) DeepCopy() *TCPConnectAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TCPConnectAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPLoadBalancer) DeepCopyInto(out *TCPLoadBalancer) {
|
||||
*out = *in
|
||||
@@ -2280,3 +2766,30 @@ func (in *TextAnalyze) DeepCopy() *TextAnalyze {
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TimeAnalyze) DeepCopyInto(out *TimeAnalyze) {
|
||||
*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 TimeAnalyze.
|
||||
func (in *TimeAnalyze) DeepCopy() *TimeAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TimeAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -173,6 +173,24 @@ func ClusterResources(c *Collector) (map[string][]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Persistent Volumes
|
||||
pvs, pvsErrors := pvs(ctx, client)
|
||||
clusterResourcesOutput["cluster-resources/pvs.json"] = pvs
|
||||
clusterResourcesOutput["cluster-resources/pvs-errors.json"], err = marshalNonNil(pvsErrors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Persistent Volume Claims
|
||||
pvcs, pvcsErrors := pvcs(ctx, client, namespaceNames)
|
||||
for k, v := range pvcs {
|
||||
clusterResourcesOutput[path.Join("cluster-resources/pvcs", k)] = v
|
||||
}
|
||||
clusterResourcesOutput["cluster-resources/pvcs-errors.json"], err = marshalNonNil(pvcsErrors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clusterResourcesOutput, nil
|
||||
}
|
||||
|
||||
@@ -532,3 +550,39 @@ func convertToPolicyRule(status authorizationv1.SubjectRulesReviewStatus) []rbac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func pvs(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
|
||||
pv, err := client.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(pv.Items, "", " ")
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func pvcs(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
pvcsByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
pvcs, err := client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(pvcs.Items, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
pvcsByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return pvcsByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -20,7 +19,6 @@ type Collector struct {
|
||||
RBACErrors []error
|
||||
ClientConfig *rest.Config
|
||||
Namespace string
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
type Collectors []*Collector
|
||||
@@ -201,6 +199,8 @@ func (c *Collector) RunCollectorSync(globalRedactors []*troubleshootv1beta2.Reda
|
||||
result, err = Collectd(c, c.Collect.Collectd)
|
||||
} else if c.Collect.Ceph != nil {
|
||||
result, err = Ceph(c, c.Collect.Ceph)
|
||||
} else if c.Collect.RegistryImages != nil {
|
||||
result, err = Registry(c, c.Collect.RegistryImages)
|
||||
} else {
|
||||
err = errors.New("no spec found to run")
|
||||
return
|
||||
@@ -209,15 +209,6 @@ func (c *Collector) RunCollectorSync(globalRedactors []*troubleshootv1beta2.Reda
|
||||
return
|
||||
}
|
||||
|
||||
if c.PathPrefix != "" {
|
||||
// prefix file paths
|
||||
prefixed := map[string][]byte{}
|
||||
for k, v := range result {
|
||||
prefixed[filepath.Join(c.PathPrefix, k)] = v
|
||||
}
|
||||
result = prefixed
|
||||
}
|
||||
|
||||
if c.Redact {
|
||||
result, err = redactMap(result, globalRedactors)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type BlockDeviceInfo struct {
|
||||
Name string `json:"name"`
|
||||
KernelName string `json:"kernel_name"`
|
||||
ParentKernelName string `json:"parent_kernel_name"`
|
||||
Type string `json:"type"`
|
||||
Major int `json:"major"`
|
||||
Minor int `json:"minor"`
|
||||
Size uint64 `json:"size"`
|
||||
FilesystemType string `json:"filesystem_type"`
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
Serial string `json:"serial"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
Removable bool `json:"removable"`
|
||||
}
|
||||
|
||||
const lsblkColumns = "NAME,KNAME,PKNAME,TYPE,MAJ:MIN,SIZE,FSTYPE,MOUNTPOINT,SERIAL,RO,RM"
|
||||
const lsblkFormat = `NAME=%q KNAME=%q PKNAME=%q TYPE=%q MAJ:MIN="%d:%d" SIZE="%d" FSTYPE=%q MOUNTPOINT=%q SERIAL=%q RO="%d" RM="%d0"`
|
||||
|
||||
type CollectHostBlockDevices struct {
|
||||
hostCollector *troubleshootv1beta2.HostBlockDevices
|
||||
}
|
||||
|
||||
func (c *CollectHostBlockDevices) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Block Devices")
|
||||
}
|
||||
|
||||
func (c *CollectHostBlockDevices) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostBlockDevices) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var devices []BlockDeviceInfo
|
||||
|
||||
cmd := exec.Command("lsblk", "--noheadings", "--bytes", "--pairs", "-o", lsblkColumns)
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to execute lsblk")
|
||||
}
|
||||
buf := bytes.NewBuffer(stdout)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
for scanner.Scan() {
|
||||
bdi := BlockDeviceInfo{}
|
||||
var ro int
|
||||
var rm int
|
||||
fmt.Sscanf(
|
||||
scanner.Text(),
|
||||
lsblkFormat,
|
||||
&bdi.Name,
|
||||
&bdi.KernelName,
|
||||
&bdi.ParentKernelName,
|
||||
&bdi.Type,
|
||||
&bdi.Major,
|
||||
&bdi.Minor,
|
||||
&bdi.Size,
|
||||
&bdi.FilesystemType,
|
||||
&bdi.Mountpoint,
|
||||
&bdi.Serial,
|
||||
&ro,
|
||||
&rm,
|
||||
)
|
||||
bdi.ReadOnly = ro == 1
|
||||
bdi.Removable = rm == 1
|
||||
|
||||
devices = append(devices, bdi)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal block device info")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
"system/block_devices.json": b,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
const KeyPairMissing = "key-pair-missing"
|
||||
const KeyPairSwitched = "key-pair-switched"
|
||||
const KeyPairEncrypted = "key-pair-encrypted"
|
||||
const KeyPairMismatch = "key-pair-mismatch"
|
||||
const KeyPairInvalid = "key-pair-invalid"
|
||||
const KeyPairValid = "key-pair-valid"
|
||||
|
||||
type CollectHostCertificate struct {
|
||||
hostCollector *troubleshootv1beta2.Certificate
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificate) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Certificate Key Pair")
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificate) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostCertificate) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var result = KeyPairValid
|
||||
|
||||
_, err := tls.LoadX509KeyPair(c.hostCollector.CertificatePath, c.hostCollector.KeyPath)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such file") {
|
||||
result = KeyPairMissing
|
||||
} else if strings.Contains(err.Error(), "PEM inputs may have been switched") {
|
||||
result = KeyPairSwitched
|
||||
} else if strings.Contains(err.Error(), "found a certificate rather than a key") {
|
||||
result = KeyPairSwitched
|
||||
} else if strings.Contains(err.Error(), "private key does not match public key") {
|
||||
result = KeyPairMismatch
|
||||
} else if strings.Contains(err.Error(), "failed to parse private key") {
|
||||
if encrypted, _ := isEncryptedKey(c.hostCollector.KeyPath); encrypted {
|
||||
result = KeyPairEncrypted
|
||||
} else {
|
||||
result = KeyPairInvalid
|
||||
}
|
||||
} else {
|
||||
result = KeyPairInvalid
|
||||
}
|
||||
}
|
||||
|
||||
collectorName := c.hostCollector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "certificate"
|
||||
}
|
||||
name := filepath.Join("certificate", collectorName+".json")
|
||||
|
||||
return map[string][]byte{
|
||||
name: []byte(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isEncryptedKey(filename string) (bool, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Contains(data, []byte("ENCRYPTED")), nil
|
||||
}
|
||||
@@ -1,44 +1,53 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type HostCollector struct {
|
||||
Collect *troubleshootv1beta2.HostCollect
|
||||
type HostCollector interface {
|
||||
Title() string
|
||||
IsExcluded() (bool, error)
|
||||
Collect(progressChan chan<- interface{}) (map[string][]byte, error)
|
||||
}
|
||||
|
||||
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
|
||||
func GetHostCollector(collector *troubleshootv1beta2.HostCollect) (HostCollector, bool) {
|
||||
switch {
|
||||
case collector.CPU != nil:
|
||||
return &CollectHostCPU{collector.CPU}, true
|
||||
case collector.Memory != nil:
|
||||
return &CollectHostMemory{collector.Memory}, true
|
||||
case collector.TCPLoadBalancer != nil:
|
||||
return &CollectHostTCPLoadBalancer{collector.TCPLoadBalancer}, true
|
||||
case collector.HTTPLoadBalancer != nil:
|
||||
return &CollectHostHTTPLoadBalancer{collector.HTTPLoadBalancer}, true
|
||||
case collector.DiskUsage != nil:
|
||||
return &CollectHostDiskUsage{collector.DiskUsage}, true
|
||||
case collector.TCPPortStatus != nil:
|
||||
return &CollectHostTCPPortStatus{collector.TCPPortStatus}, true
|
||||
case collector.HTTP != nil:
|
||||
return &CollectHostHTTP{collector.HTTP}, true
|
||||
case collector.Time != nil:
|
||||
return &CollectHostTime{collector.Time}, true
|
||||
case collector.BlockDevices != nil:
|
||||
return &CollectHostBlockDevices{collector.BlockDevices}, true
|
||||
case collector.TCPConnect != nil:
|
||||
return &CollectHostTCPConnect{collector.TCPConnect}, true
|
||||
case collector.IPV4Interfaces != nil:
|
||||
return &CollectHostIPV4Interfaces{collector.IPV4Interfaces}, true
|
||||
case collector.FilesystemPerformance != nil:
|
||||
return &CollectHostFilesystemPerformance{collector.FilesystemPerformance}, true
|
||||
case collector.Certificate != nil:
|
||||
return &CollectHostCertificate{collector.Certificate}, true
|
||||
case collector.HostServices != nil:
|
||||
return &CollectHostServices{collector.HostServices}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
func hostCollectorTitleOrDefault(meta troubleshootv1beta2.HostCollectorMeta, defaultTitle string) string {
|
||||
if meta.CollectorName != "" {
|
||||
return meta.CollectorName
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *HostCollector) GetDisplayName() string {
|
||||
return c.Collect.GetName()
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
+14
-1
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,19 @@ type CPUInfo struct {
|
||||
PhysicalCount int `json:"physicalCount"`
|
||||
}
|
||||
|
||||
func HostCPU(c *HostCollector) (map[string][]byte, error) {
|
||||
type CollectHostCPU struct {
|
||||
hostCollector *troubleshootv1beta2.CPU
|
||||
}
|
||||
|
||||
func (c *CollectHostCPU) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "CPU Info")
|
||||
}
|
||||
|
||||
func (c *CollectHostCPU) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostCPU) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
cpuInfo := CPUInfo{}
|
||||
|
||||
logicalCount, err := cpu.Counts(true)
|
||||
|
||||
@@ -3,8 +3,11 @@ package collect
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
)
|
||||
|
||||
@@ -13,16 +16,33 @@ type DiskUsageInfo struct {
|
||||
UsedBytes uint64 `json:"used_bytes"`
|
||||
}
|
||||
|
||||
func HostDiskUsage(c *HostCollector) (map[string][]byte, error) {
|
||||
type CollectHostDiskUsage struct {
|
||||
hostCollector *troubleshootv1beta2.DiskUsage
|
||||
}
|
||||
|
||||
func (c *CollectHostDiskUsage) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, fmt.Sprintf("Disk Usage %s", c.hostCollector.CollectorName))
|
||||
}
|
||||
|
||||
func (c *CollectHostDiskUsage) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostDiskUsage) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
result := map[string][]byte{}
|
||||
|
||||
if c.Collect.DiskUsage == nil {
|
||||
if c.hostCollector == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
du, err := disk.Usage(c.Collect.DiskUsage.Path)
|
||||
pathExists, err := traverseFiletreeDirExists(c.hostCollector.Path)
|
||||
if err != nil {
|
||||
return result, errors.Wrapf(err, "collect disk usage for %s", c.Collect.DiskUsage.Path)
|
||||
return result, errors.Wrap(err, "traverse file tree")
|
||||
}
|
||||
|
||||
du, err := disk.Usage(pathExists)
|
||||
if err != nil {
|
||||
return result, errors.Wrapf(err, "collect disk usage for %s", pathExists)
|
||||
}
|
||||
diskSpaceInfo := DiskUsageInfo{
|
||||
TotalBytes: du.Total,
|
||||
@@ -32,7 +52,7 @@ func HostDiskUsage(c *HostCollector) (map[string][]byte, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal disk space info")
|
||||
}
|
||||
key := HostDiskUsageKey(c.Collect.DiskUsage.CollectorName)
|
||||
key := HostDiskUsageKey(c.hostCollector.CollectorName)
|
||||
result[key] = b
|
||||
|
||||
return result, nil
|
||||
@@ -41,3 +61,21 @@ func HostDiskUsage(c *HostCollector) (map[string][]byte, error) {
|
||||
func HostDiskUsageKey(name string) string {
|
||||
return fmt.Sprintf("diskUsage/%s.json", name)
|
||||
}
|
||||
|
||||
func traverseFiletreeDirExists(filename string) (string, error) {
|
||||
filename = filepath.Clean(filename)
|
||||
for i := 0; i < 50; i++ {
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
return filename, nil
|
||||
} else if os.IsNotExist(err) {
|
||||
filename = filepath.Dir(filename)
|
||||
if filename == "/" {
|
||||
return filename, nil
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", errors.New("max recursion exceeded")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type CollectHostFilesystemPerformance struct {
|
||||
hostCollector *troubleshootv1beta2.FilesystemPerformance
|
||||
}
|
||||
|
||||
func (c *CollectHostFilesystemPerformance) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Filesystem Performance")
|
||||
}
|
||||
|
||||
func (c *CollectHostFilesystemPerformance) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostFilesystemPerformance) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
return collectHostFilesystemPerformance(c.hostCollector)
|
||||
}
|
||||
|
||||
type FSPerfResults struct {
|
||||
Min time.Duration
|
||||
Max time.Duration
|
||||
Average time.Duration
|
||||
P1 time.Duration
|
||||
P5 time.Duration
|
||||
P10 time.Duration
|
||||
P20 time.Duration
|
||||
P30 time.Duration
|
||||
P40 time.Duration
|
||||
P50 time.Duration
|
||||
P60 time.Duration
|
||||
P70 time.Duration
|
||||
P80 time.Duration
|
||||
P90 time.Duration
|
||||
P95 time.Duration
|
||||
P99 time.Duration
|
||||
P995 time.Duration
|
||||
P999 time.Duration
|
||||
P9995 time.Duration
|
||||
P9999 time.Duration
|
||||
IOPS int
|
||||
}
|
||||
|
||||
func getPercentileIndex(p float64, items int) int {
|
||||
if p >= 1 {
|
||||
return items - 1
|
||||
}
|
||||
return int(math.Ceil(p*float64(items))) - 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func collectHostFilesystemPerformance(hostCollector *troubleshootv1beta2.FilesystemPerformance) (map[string][]byte, error) {
|
||||
return nil, errors.New("Filesystem performance collector is only implemented for Linux")
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type Durations []time.Duration
|
||||
|
||||
func (d Durations) Len() int {
|
||||
return len(d)
|
||||
}
|
||||
|
||||
func (d Durations) Less(i, j int) bool {
|
||||
return d[i] < d[j]
|
||||
}
|
||||
|
||||
func (d Durations) Swap(i, j int) {
|
||||
d[i], d[j] = d[j], d[i]
|
||||
}
|
||||
|
||||
func collectHostFilesystemPerformance(hostCollector *troubleshootv1beta2.FilesystemPerformance) (map[string][]byte, error) {
|
||||
var operationSize uint64 = 1024
|
||||
if hostCollector.OperationSizeBytes != 0 {
|
||||
operationSize = hostCollector.OperationSizeBytes
|
||||
}
|
||||
|
||||
var fileSize uint64 = 10 * 1024 * 1024
|
||||
if hostCollector.FileSize != "" {
|
||||
quantity, err := resource.ParseQuantity(hostCollector.FileSize)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse fileSize %q", hostCollector.FileSize)
|
||||
}
|
||||
fileSizeInt64, ok := quantity.AsInt64()
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(err, "failed to parse fileSize %q", hostCollector.FileSize)
|
||||
}
|
||||
fileSize = uint64(fileSizeInt64)
|
||||
}
|
||||
|
||||
if hostCollector.Directory == "" {
|
||||
return nil, errors.New("Directory is required to collect filesystem performance info")
|
||||
}
|
||||
// TODO: clean up this directory if its created
|
||||
if err := os.MkdirAll(hostCollector.Directory, 0700); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to mkdir %q", hostCollector.Directory)
|
||||
}
|
||||
filename := filepath.Join(hostCollector.Directory, "fsperf")
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "open %s", filename)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
if err := os.Remove(filename); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// Sequential writes benchmark
|
||||
var written uint64 = 0
|
||||
var results Durations
|
||||
|
||||
for {
|
||||
if written >= fileSize {
|
||||
break
|
||||
}
|
||||
|
||||
data := make([]byte, int(operationSize))
|
||||
rand.Read(data)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
n, err := f.Write(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "write to %s", filename)
|
||||
}
|
||||
if hostCollector.Sync {
|
||||
if err := f.Sync(); err != nil {
|
||||
return nil, errors.Wrapf(err, "sync %s", filename)
|
||||
}
|
||||
} else if hostCollector.Datasync {
|
||||
if err := syscall.Fdatasync(int(f.Fd())); err != nil {
|
||||
return nil, errors.Wrapf(err, "datasync %s", filename)
|
||||
}
|
||||
}
|
||||
|
||||
d := time.Now().Sub(start)
|
||||
results = append(results, d)
|
||||
|
||||
written += uint64(n)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return nil, errors.New("No filesystem performance results collected")
|
||||
}
|
||||
|
||||
sort.Sort(results)
|
||||
|
||||
var sum time.Duration
|
||||
for _, d := range results {
|
||||
sum += d
|
||||
}
|
||||
|
||||
fsPerf := &FSPerfResults{
|
||||
Min: results[0],
|
||||
Max: results[len(results)-1],
|
||||
Average: sum / time.Duration(len(results)),
|
||||
P1: results[getPercentileIndex(.01, len(results))],
|
||||
P5: results[getPercentileIndex(.05, len(results))],
|
||||
P10: results[getPercentileIndex(.1, len(results))],
|
||||
P20: results[getPercentileIndex(.2, len(results))],
|
||||
P30: results[getPercentileIndex(.3, len(results))],
|
||||
P40: results[getPercentileIndex(.4, len(results))],
|
||||
P50: results[getPercentileIndex(.5, len(results))],
|
||||
P60: results[getPercentileIndex(.6, len(results))],
|
||||
P70: results[getPercentileIndex(.7, len(results))],
|
||||
P80: results[getPercentileIndex(.8, len(results))],
|
||||
P90: results[getPercentileIndex(.9, len(results))],
|
||||
P95: results[getPercentileIndex(.95, len(results))],
|
||||
P99: results[getPercentileIndex(.99, len(results))],
|
||||
P995: results[getPercentileIndex(.995, len(results))],
|
||||
P999: results[getPercentileIndex(.999, len(results))],
|
||||
P9995: results[getPercentileIndex(.9995, len(results))],
|
||||
P9999: results[getPercentileIndex(.9999, len(results))],
|
||||
}
|
||||
|
||||
// Random IOPS benchmark
|
||||
|
||||
// Re-open the file read+write in direct mode to prevent caching
|
||||
if err := f.Close(); err != nil {
|
||||
return nil, errors.Wrapf(err, "close %s", filename)
|
||||
}
|
||||
f, err = os.OpenFile(filename, os.O_RDWR|syscall.O_DIRECT, 0600)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "open direct %s", filename)
|
||||
}
|
||||
|
||||
offsets := make([]int64, len(results))
|
||||
|
||||
for index, p := range rand.Perm(len(results)) {
|
||||
offsets[index] = int64(p) * int64(operationSize)
|
||||
}
|
||||
|
||||
// Use multiple workers to keep the filesystem busy. Since operations are serialized on a single
|
||||
// file, more than 2 does not improve IOPS.
|
||||
workers := 2
|
||||
wg := sync.WaitGroup{}
|
||||
m := sync.Mutex{}
|
||||
|
||||
errs := make(chan error, workers)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
data := make([]byte, int(operationSize))
|
||||
fd := int(f.Fd())
|
||||
|
||||
for idx, offset := range offsets {
|
||||
if idx%workers != i {
|
||||
continue
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
n, err := syscall.Pread(fd, data, offset)
|
||||
m.Unlock()
|
||||
|
||||
if err != nil {
|
||||
errs <- errors.Wrapf(err, "failed to pread %d bytes to %s at offset %d", len(data), filename, offset)
|
||||
}
|
||||
if n != len(data) {
|
||||
errs <- errors.Wrapf(err, "pread %d of %d bytes to %s at offset %d", n, len(data), filename, offset)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if len(errs) > 0 {
|
||||
return nil, <-errs
|
||||
}
|
||||
|
||||
d := time.Now().Sub(start)
|
||||
nsPerIO := d / time.Duration(len(offsets))
|
||||
iops := time.Second / nsPerIO
|
||||
|
||||
fsPerf.IOPS = int(iops)
|
||||
|
||||
collectorName := hostCollector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "filesystemPerformance"
|
||||
}
|
||||
name := filepath.Join("filesystemPerformance", collectorName+".json")
|
||||
b, err := json.Marshal(fsPerf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal fs perf results")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
name: b,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetPercentileIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
length int
|
||||
p float64
|
||||
answer int
|
||||
}{
|
||||
{
|
||||
length: 2,
|
||||
p: 0.49,
|
||||
answer: 0,
|
||||
},
|
||||
{
|
||||
length: 2,
|
||||
p: 0.5,
|
||||
answer: 0,
|
||||
},
|
||||
{
|
||||
length: 2,
|
||||
p: 0.51,
|
||||
answer: 1,
|
||||
},
|
||||
{
|
||||
length: 100,
|
||||
p: 0.01,
|
||||
answer: 0,
|
||||
},
|
||||
{
|
||||
length: 100,
|
||||
p: 0.99,
|
||||
answer: 98,
|
||||
},
|
||||
{
|
||||
length: 100,
|
||||
p: 0.995,
|
||||
answer: 99,
|
||||
},
|
||||
{
|
||||
length: 10000,
|
||||
p: 0.999,
|
||||
answer: 9989,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("(%f, %d) == %d", test.p, test.length, test.answer)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
output := getPercentileIndex(test.p, test.length)
|
||||
if output != test.answer {
|
||||
t.Errorf("Got %d, want %d", output, test.answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func collectHostFilesystemPerformance(hostCollector *troubleshootv1beta2.FilesystemPerformance) (map[string][]byte, error) {
|
||||
return nil, errors.New("Filesystem performance collector is only implemented for Linux")
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type CollectHostHTTP struct {
|
||||
hostCollector *troubleshootv1beta2.HostHTTP
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTP) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "HTTP Request")
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTP) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTP) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
httpCollector := c.hostCollector
|
||||
|
||||
var response *http.Response
|
||||
var err error
|
||||
|
||||
if httpCollector.Get != nil {
|
||||
response, err = doGet(httpCollector.Get)
|
||||
} else if httpCollector.Post != nil {
|
||||
response, err = doPost(httpCollector.Post)
|
||||
} else if httpCollector.Put != nil {
|
||||
response, err = doPut(httpCollector.Put)
|
||||
} else {
|
||||
return nil, errors.New("no supported http request type")
|
||||
}
|
||||
|
||||
output, err := responseToOutput(response, err, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileName := "result.json"
|
||||
if httpCollector.CollectorName != "" {
|
||||
fileName = httpCollector.CollectorName + ".json"
|
||||
}
|
||||
httpOutput := map[string][]byte{
|
||||
filepath.Join("http", fileName): output,
|
||||
}
|
||||
|
||||
return httpOutput, nil
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/debug"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type CollectHostHTTPLoadBalancer struct {
|
||||
hostCollector *troubleshootv1beta2.HTTPLoadBalancer
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTPLoadBalancer) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "HTTP Load Balancer")
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTPLoadBalancer) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostHTTPLoadBalancer) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.hostCollector.Port)
|
||||
|
||||
timeout := 60 * time.Minute
|
||||
if c.hostCollector.Timeout != "" {
|
||||
var err error
|
||||
timeout, err = time.ParseDuration(c.hostCollector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse timeout %q", c.hostCollector.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
requestToken := ksuid.New().Bytes()
|
||||
responseToken := ksuid.New().Bytes()
|
||||
|
||||
listenErr := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
mux := http.NewServeMux()
|
||||
server := http.Server{
|
||||
Addr: listenAddress,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
return
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(body, requestToken) {
|
||||
return
|
||||
}
|
||||
_, err = w.Write(responseToken)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
server.Shutdown(context.Background())
|
||||
})
|
||||
|
||||
err := http.ListenAndServe(listenAddress, mux)
|
||||
if err != http.ErrServerClosed {
|
||||
listenErr <- err
|
||||
}
|
||||
}()
|
||||
|
||||
var networkStatus NetworkStatus
|
||||
|
||||
stopAfter := time.Now().Add(timeout)
|
||||
for {
|
||||
if len(listenErr) > 0 {
|
||||
err := <-listenErr
|
||||
if strings.Contains(err.Error(), "address already in use") {
|
||||
networkStatus = NetworkStatusAddressInUse
|
||||
break
|
||||
}
|
||||
if strings.Contains(err.Error(), "permission denied") {
|
||||
networkStatus = NetworkStatusBindPermissionDenied
|
||||
break
|
||||
}
|
||||
debug.Println(err.Error())
|
||||
networkStatus = NetworkStatusErrorOther
|
||||
break
|
||||
}
|
||||
if time.Now().After(stopAfter) {
|
||||
break
|
||||
}
|
||||
|
||||
networkStatus = attemptPOST(c.hostCollector.Address, requestToken, responseToken)
|
||||
|
||||
if networkStatus == NetworkStatusErrorOther || networkStatus == NetworkStatusConnectionTimeout {
|
||||
progressChan <- errors.Errorf("http post %s: network status %q", c.hostCollector.Address, networkStatus)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
result := NetworkStatusResult{
|
||||
Status: networkStatus,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal result")
|
||||
}
|
||||
|
||||
name := path.Join("httpLoadBalancer", "httpLoadBalancer.json")
|
||||
if c.hostCollector.CollectorName != "" {
|
||||
name = path.Join("httpLoadBalancer", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
name: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func attemptPOST(address string, request []byte, response []byte) NetworkStatus {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Create a new transport every time to ensure a new TCP connection so the load balancer does
|
||||
// not forward every request to the same backend
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 50 * time.Millisecond,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
client := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(request)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", address, buf)
|
||||
if err != nil {
|
||||
debug.Println(err.Error())
|
||||
return NetworkStatusErrorOther
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return NetworkStatusConnectionRefused
|
||||
}
|
||||
if strings.Contains(err.Error(), "i/o timeout") {
|
||||
return NetworkStatusConnectionTimeout
|
||||
}
|
||||
|
||||
return NetworkStatusErrorOther
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return NetworkStatusErrorOther
|
||||
}
|
||||
if !bytes.Equal(body, response) {
|
||||
return NetworkStatusErrorOther
|
||||
}
|
||||
|
||||
return NetworkStatusConnected
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type CollectHostIPV4Interfaces struct {
|
||||
hostCollector *troubleshootv1beta2.IPV4Interfaces
|
||||
}
|
||||
|
||||
func (c *CollectHostIPV4Interfaces) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "IPv4 Interfaces")
|
||||
}
|
||||
|
||||
func (c *CollectHostIPV4Interfaces) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostIPV4Interfaces) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var ipv4Interfaces []net.Interface
|
||||
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "list host network interfaces")
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
ip, _ := getIPv4FromInterface(&iface)
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
ipv4Interfaces = append(ipv4Interfaces, iface)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ipv4Interfaces)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal network interfaces")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
"system/ipv4Interfaces.json": b,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
@@ -11,7 +12,19 @@ type MemoryInfo struct {
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
func HostMemory(c *HostCollector) (map[string][]byte, error) {
|
||||
type CollectHostMemory struct {
|
||||
hostCollector *troubleshootv1beta2.Memory
|
||||
}
|
||||
|
||||
func (c *CollectHostMemory) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Amount of Memory")
|
||||
}
|
||||
|
||||
func (c *CollectHostMemory) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostMemory) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
memoryInfo := MemoryInfo{}
|
||||
|
||||
vmstat, err := mem.VirtualMemory()
|
||||
|
||||
+20
-19
@@ -2,30 +2,31 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/debug"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type NetworkStatus string
|
||||
|
||||
const (
|
||||
NetworkStatusAddressInUse = "address-in-use"
|
||||
NetworkStatusConnectionRefused = "connection-refused"
|
||||
NetworkStatusConnectionTimeout = "connection-timeout"
|
||||
NetworkStatusConnected = "connected"
|
||||
NetworkStatusErrorOther = "error"
|
||||
NetworkStatusAddressInUse = "address-in-use"
|
||||
NetworkStatusConnectionRefused = "connection-refused"
|
||||
NetworkStatusConnectionTimeout = "connection-timeout"
|
||||
NetworkStatusConnected = "connected"
|
||||
NetworkStatusErrorOther = "error"
|
||||
NetworkStatusBindPermissionDenied = "bind-permission-denied"
|
||||
)
|
||||
|
||||
type NetworkStatusResult struct {
|
||||
Status NetworkStatus `json:"status"`
|
||||
}
|
||||
|
||||
func checkTCPConnection(listenAddress string, dialAddress string, timeout time.Duration) (NetworkStatus, error) {
|
||||
func checkTCPConnection(progressChan chan<- interface{}, 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") {
|
||||
@@ -65,7 +66,8 @@ func checkTCPConnection(listenAddress string, dialAddress string, timeout time.D
|
||||
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)
|
||||
progressChan <- err
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
@@ -78,26 +80,27 @@ func checkTCPConnection(listenAddress string, dialAddress string, timeout time.D
|
||||
return NetworkStatusConnected, nil
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
progressChan <- errors.New("failed to verify connection to server")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []byte) bool {
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
debug.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)
|
||||
debug.Printf("Server failed to set read deadline: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
_, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Printf("Server failed to read: %v", err)
|
||||
debug.Printf("Server failed to read: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -106,12 +109,12 @@ func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []by
|
||||
}
|
||||
|
||||
if err := conn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
||||
fmt.Printf("Server failed to set write deadline: %v", err)
|
||||
debug.Printf("Server failed to set write deadline: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := conn.Write(responseToken); err != nil {
|
||||
fmt.Printf("Server failed to write: %v", err)
|
||||
debug.Printf("Server failed to write: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -121,30 +124,28 @@ func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []by
|
||||
func verifyConnectionToServer(conn net.Conn, requestToken []byte, responseToken []byte) bool {
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
debug.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)
|
||||
debug.Printf("Client failed to set write deadline: %v\n", 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)
|
||||
debug.Printf("Client failed to set read deadline: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Printf("Client failed to read: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type ServiceInfo struct {
|
||||
Unit string `json:"Unit"`
|
||||
Load string `json:"Load"`
|
||||
Active string `json:"Active"`
|
||||
Sub string `json:"Sub"`
|
||||
}
|
||||
|
||||
const systemctlFormat = `%s %s %s %s` // this leaves off the description
|
||||
const HostServicesPath = `system/systemctl_services.json`
|
||||
|
||||
type CollectHostServices struct {
|
||||
hostCollector *troubleshootv1beta2.HostServices
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Block Devices")
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var devices []ServiceInfo
|
||||
|
||||
cmd := exec.Command("systemctl", "list-units", "--type=service", "--no-legend", "--all")
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to execute systemctl")
|
||||
}
|
||||
buf := bytes.NewBuffer(stdout)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
for scanner.Scan() {
|
||||
bdi := ServiceInfo{}
|
||||
fmt.Sscanf(
|
||||
scanner.Text(),
|
||||
systemctlFormat,
|
||||
&bdi.Unit,
|
||||
&bdi.Load,
|
||||
&bdi.Active,
|
||||
&bdi.Sub,
|
||||
)
|
||||
|
||||
devices = append(devices, bdi)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal systemctl service info")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
HostServicesPath: b,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type CollectHostTCPConnect struct {
|
||||
hostCollector *troubleshootv1beta2.TCPConnect
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPConnect) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Connection Attempt")
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPConnect) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPConnect) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
address := c.hostCollector.Address
|
||||
|
||||
timeout := 10 * time.Second
|
||||
if c.hostCollector.Timeout != "" {
|
||||
var err error
|
||||
timeout, err = time.ParseDuration(c.hostCollector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse timeout %q", c.hostCollector.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
result := NetworkStatusResult{
|
||||
Status: attemptConnect(address, timeout),
|
||||
}
|
||||
|
||||
b, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal result")
|
||||
}
|
||||
|
||||
name := path.Join("connect", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
|
||||
|
||||
return map[string][]byte{
|
||||
name: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func attemptConnect(address string, timeout time.Duration) NetworkStatus {
|
||||
conn, err := net.DialTimeout("tcp", address, timeout)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "i/o timeout") {
|
||||
return NetworkStatusConnectionTimeout
|
||||
}
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return NetworkStatusConnectionRefused
|
||||
}
|
||||
return NetworkStatusErrorOther
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
return NetworkStatusConnected
|
||||
}
|
||||
@@ -7,32 +7,35 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type ConnectionResult int
|
||||
type CollectHostTCPLoadBalancer struct {
|
||||
hostCollector *troubleshootv1beta2.TCPLoadBalancer
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectionRefused ConnectionResult = iota
|
||||
Connected
|
||||
ConnectionTimeout
|
||||
ConnectionAddressInUse
|
||||
ErrorOther
|
||||
)
|
||||
func (c *CollectHostTCPLoadBalancer) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Load Balancer")
|
||||
}
|
||||
|
||||
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
|
||||
func (c *CollectHostTCPLoadBalancer) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPLoadBalancer) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.hostCollector.Port)
|
||||
dialAddress := c.hostCollector.Address
|
||||
|
||||
timeout := 60 * time.Minute
|
||||
if c.Collect.TCPLoadBalancer.Timeout != "" {
|
||||
if c.hostCollector.Timeout != "" {
|
||||
var err error
|
||||
timeout, err = time.ParseDuration(c.Collect.TCPLoadBalancer.Timeout)
|
||||
timeout, err = time.ParseDuration(c.hostCollector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse durection")
|
||||
}
|
||||
}
|
||||
|
||||
networkStatus, err := checkTCPConnection(listenAddress, dialAddress, timeout)
|
||||
networkStatus, err := checkTCPConnection(progressChan, listenAddress, dialAddress, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -47,8 +50,8 @@ func HostTCPLoadBalancer(c *HostCollector) (map[string][]byte, error) {
|
||||
}
|
||||
|
||||
name := path.Join("tcpLoadBalancer", "tcpLoadBalancer.json")
|
||||
if c.Collect.TCPLoadBalancer.CollectorName != "" {
|
||||
name = path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", c.Collect.TCPLoadBalancer.CollectorName))
|
||||
if c.hostCollector.CollectorName != "" {
|
||||
name = path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
|
||||
@@ -8,22 +8,35 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func HostTCPPortStatus(c *HostCollector) (map[string][]byte, error) {
|
||||
dialAddress := ""
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.Collect.TCPPortStatus.Port)
|
||||
type CollectHostTCPPortStatus struct {
|
||||
hostCollector *troubleshootv1beta2.TCPPortStatus
|
||||
}
|
||||
|
||||
if c.Collect.TCPPortStatus.Interface != "" {
|
||||
iface, err := net.InterfaceByName(c.Collect.TCPPortStatus.Interface)
|
||||
func (c *CollectHostTCPPortStatus) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Port Status")
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPPortStatus) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostTCPPortStatus) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
dialAddress := ""
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.hostCollector.Port)
|
||||
|
||||
if c.hostCollector.Interface != "" {
|
||||
iface, err := net.InterfaceByName(c.hostCollector.Interface)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "lookup interface %s", c.Collect.TCPPortStatus.Interface)
|
||||
return nil, errors.Wrapf(err, "lookup interface %s", c.hostCollector.Interface)
|
||||
}
|
||||
ip, err := getIPv4FromInterface(iface)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get ipv4 address for interface %s", c.Collect.TCPPortStatus.Interface)
|
||||
return nil, errors.Wrapf(err, "get ipv4 address for interface %s", c.hostCollector.Interface)
|
||||
}
|
||||
listenAddress = fmt.Sprintf("%s:%d", ip, c.Collect.TCPPortStatus.Port)
|
||||
listenAddress = fmt.Sprintf("%s:%d", ip, c.hostCollector.Port)
|
||||
dialAddress = listenAddress
|
||||
}
|
||||
|
||||
@@ -32,10 +45,10 @@ func HostTCPPortStatus(c *HostCollector) (map[string][]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dialAddress = fmt.Sprintf("%s:%d", ip, c.Collect.TCPPortStatus.Port)
|
||||
dialAddress = fmt.Sprintf("%s:%d", ip, c.hostCollector.Port)
|
||||
}
|
||||
|
||||
networkStatus, err := checkTCPConnection(listenAddress, dialAddress, 10*time.Second)
|
||||
networkStatus, err := checkTCPConnection(progressChan, listenAddress, dialAddress, 10*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -49,8 +62,8 @@ func HostTCPPortStatus(c *HostCollector) (map[string][]byte, error) {
|
||||
}
|
||||
|
||||
name := path.Join("tcpPortStatus", "tcpPortStatus.json")
|
||||
if c.Collect.TCPPortStatus.CollectorName != "" {
|
||||
name = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", c.Collect.TCPPortStatus.CollectorName))
|
||||
if c.hostCollector.CollectorName != "" {
|
||||
name = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
|
||||
}
|
||||
return map[string][]byte{
|
||||
name: b,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/godbus/dbus"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type NTPStatus string
|
||||
|
||||
type TimeInfo struct {
|
||||
Timezone string `json:"timezone"`
|
||||
NTPSynchronized bool `json:"ntp_synchronized"`
|
||||
NTPActive bool `json:"ntp_active"`
|
||||
}
|
||||
|
||||
type CollectHostTime struct {
|
||||
hostCollector *troubleshootv1beta2.HostTime
|
||||
}
|
||||
|
||||
func (c *CollectHostTime) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "TCP Port Status")
|
||||
}
|
||||
|
||||
func (c *CollectHostTime) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostTime) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
timeInfo := TimeInfo{}
|
||||
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to dbus")
|
||||
}
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Printf("Failed to close dbus connection: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
prop := "org.freedesktop.timedate1.Timezone"
|
||||
variant, err := conn.Object("org.freedesktop.timedate1", "/org/freedesktop/timedate1").GetProperty(prop)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read property %s", prop)
|
||||
}
|
||||
timeInfo.Timezone = strings.Trim(variant.String(), `"`)
|
||||
// UTC is reported as Etc/UTC on Ubuntu
|
||||
if strings.ToLower(timeInfo.Timezone) == "etc/utc" {
|
||||
timeInfo.Timezone = "UTC"
|
||||
}
|
||||
|
||||
prop = "org.freedesktop.timedate1.NTPSynchronized"
|
||||
variant, err = conn.Object("org.freedesktop.timedate1", "/org/freedesktop/timedate1").GetProperty(prop)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read property %s", prop)
|
||||
}
|
||||
switch variant.String() {
|
||||
case "true":
|
||||
timeInfo.NTPSynchronized = true
|
||||
case "false":
|
||||
timeInfo.NTPSynchronized = false
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected value for property %s: %s", prop, variant.String())
|
||||
}
|
||||
|
||||
prop = "org.freedesktop.timedate1.NTP"
|
||||
variant, err = conn.Object("org.freedesktop.timedate1", "/org/freedesktop/timedate1").GetProperty(prop)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read property %s", prop)
|
||||
}
|
||||
switch variant.String() {
|
||||
case "true":
|
||||
timeInfo.NTPActive = true
|
||||
case "false":
|
||||
timeInfo.NTPActive = false
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected value for property %s: %s", prop, variant.String())
|
||||
}
|
||||
|
||||
b, err := json.Marshal(timeInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal time info")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
"system/time.json": b,
|
||||
}, nil
|
||||
}
|
||||
+4
-4
@@ -12,13 +12,13 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type httpResponse struct {
|
||||
type HTTPResponse struct {
|
||||
Status int `json:"status"`
|
||||
Body string `json:"body"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
type httpError struct {
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func doPut(put *troubleshootv1beta2.Put) (*http.Response, error) {
|
||||
func responseToOutput(response *http.Response, err error, doRedact bool) ([]byte, error) {
|
||||
output := make(map[string]interface{})
|
||||
if err != nil {
|
||||
output["error"] = httpError{
|
||||
output["error"] = HTTPError{
|
||||
Message: err.Error(),
|
||||
}
|
||||
} else {
|
||||
@@ -120,7 +120,7 @@ func responseToOutput(response *http.Response, err error, doRedact bool) ([]byte
|
||||
headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
|
||||
output["response"] = httpResponse{
|
||||
output["response"] = HTTPResponse{
|
||||
Status: response.StatusCode,
|
||||
Body: string(body),
|
||||
Headers: headers,
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
imagedocker "github.com/containers/image/v5/docker"
|
||||
dockerref "github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
registryv2 "github.com/docker/distribution/registry/api/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type RegistryImage struct {
|
||||
Exists bool `json:"exists"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryInfo struct {
|
||||
Images map[string]RegistryImage `json:"images"`
|
||||
}
|
||||
|
||||
type registryAuthConfig struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func Registry(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages) (map[string][]byte, error) {
|
||||
registryInfo := RegistryInfo{
|
||||
Images: map[string]RegistryImage{},
|
||||
}
|
||||
|
||||
for _, image := range registryCollector.Images {
|
||||
exists, err := imageExists(c, registryCollector, image)
|
||||
if err != nil {
|
||||
registryInfo.Images[image] = RegistryImage{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
registryInfo.Images[image] = RegistryImage{
|
||||
Exists: exists,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(registryInfo, "", " ")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := registryCollector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "images"
|
||||
}
|
||||
|
||||
registryOutput := map[string][]byte{
|
||||
fmt.Sprintf("registry/%s.json", collectorName): b,
|
||||
}
|
||||
|
||||
return registryOutput, nil
|
||||
}
|
||||
|
||||
func imageExists(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, image string) (bool, error) {
|
||||
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image))
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse image name %s", image)
|
||||
}
|
||||
|
||||
authConfig, err := getImageAuthConfig(c, registryCollector, imageRef)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get auth config")
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for i := 0; i < 3; i++ {
|
||||
sysCtx := types.SystemContext{
|
||||
DockerDisableV1Ping: true,
|
||||
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
|
||||
}
|
||||
if authConfig != nil {
|
||||
sysCtx.DockerAuthConfig = &types.DockerAuthConfig{
|
||||
Username: authConfig.username,
|
||||
Password: authConfig.password,
|
||||
}
|
||||
}
|
||||
|
||||
remoteImage, err := imageRef.NewImage(context.Background(), &sysCtx)
|
||||
if err == nil {
|
||||
remoteImage.Close()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "no image found in manifest list for architecture") {
|
||||
// manifest was downloaded, but no matching architecture found in manifest
|
||||
// should this count as image does not exist?
|
||||
// this binary's architecture is not necessarily what will run in the cluster
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if isNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "EOF") {
|
||||
lastErr = err
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return false, errors.Wrap(err, "failed to get image manifest")
|
||||
}
|
||||
|
||||
return false, errors.Wrap(lastErr, "failed to retry")
|
||||
}
|
||||
|
||||
func getImageAuthConfig(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, imageRef types.ImageReference) (*registryAuthConfig, error) {
|
||||
if registryCollector.ImagePullSecrets == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Data != nil {
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, registryCollector.ImagePullSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from data")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Name != "" {
|
||||
namespace := registryCollector.Namespace
|
||||
if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
config, err := getImageAuthConfigFromSecret(c, imageRef, registryCollector.ImagePullSecrets, namespace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("image pull secret spec is not valid")
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromData(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
|
||||
if pullSecrets.SecretType != "kubernetes.io/dockerconfigjson" {
|
||||
return nil, errors.Errorf("secret type is not supported: %s", pullSecrets.SecretType)
|
||||
}
|
||||
|
||||
configJsonBase64 := pullSecrets.Data[".dockerconfigjson"]
|
||||
registry := dockerref.Domain(imageRef.DockerReference())
|
||||
|
||||
configJson, err := base64.StdEncoding.DecodeString(configJsonBase64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode docker config string")
|
||||
}
|
||||
|
||||
dockerCfgJSON := struct {
|
||||
Auths map[string]struct {
|
||||
Auth []byte `json:"auth"`
|
||||
} `json:"auths"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal([]byte(configJson), &dockerCfgJSON)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal config json")
|
||||
}
|
||||
|
||||
auth, ok := dockerCfgJSON.Auths[registry]
|
||||
if !ok {
|
||||
// Suport a mix of public and private images
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(string(auth.Auth), ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.Errorf("expected 2 parts in the string, but found %d", len(parts))
|
||||
}
|
||||
|
||||
authConfig := registryAuthConfig{
|
||||
username: parts[0],
|
||||
password: parts[1],
|
||||
}
|
||||
|
||||
return &authConfig, nil
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromSecret(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, pullSecrets.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get secret")
|
||||
}
|
||||
|
||||
foundSecrets := &v1beta2.ImagePullSecrets{
|
||||
Name: secret.Name,
|
||||
SecretType: string(secret.Type),
|
||||
Data: map[string]string{
|
||||
".dockerconfigjson": base64.StdEncoding.EncodeToString(secret.Data[".dockerconfigjson"]),
|
||||
},
|
||||
}
|
||||
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, foundSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret data")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case errcode.Errors:
|
||||
for _, e := range err {
|
||||
if isNotFound(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case errcode.Error:
|
||||
return err.Message == registryv2.ErrorCodeManifestUnknown.Message()
|
||||
}
|
||||
|
||||
// this type will cause panic when compared to error type
|
||||
if _, ok := err.(imagedocker.ErrUnauthorizedForCredentials); ok {
|
||||
return false
|
||||
}
|
||||
|
||||
cause := errors.Cause(err)
|
||||
if cause, ok := cause.(error); ok {
|
||||
if cause == err {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return isNotFound(cause)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stderr, "[debug]", log.Lshortfile)
|
||||
|
||||
func Print(v ...interface{}) {
|
||||
if viper.GetBool("debug") {
|
||||
log.Print(v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Printf(format string, v ...interface{}) {
|
||||
if viper.GetBool("debug") {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Println(v ...interface{}) {
|
||||
if viper.GetBool("debug") {
|
||||
log.Println(v...)
|
||||
}
|
||||
}
|
||||
@@ -64,20 +64,8 @@ func doAnalyze(allCollectedData map[string][]byte, analyzers []*troubleshootv1be
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
analyzeResult := analyze.HostAnalyze(hostAnalyzer, getCollectedFileContents, getChildCollectedFileContents)
|
||||
analyzeResults = append(analyzeResults, analyzeResult...)
|
||||
}
|
||||
return analyzeResults
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ type CollectOpts struct {
|
||||
ProgressChan chan interface{}
|
||||
}
|
||||
|
||||
type CollectProgress struct {
|
||||
CurrentName string
|
||||
CurrentStatus string
|
||||
CompletedCount int
|
||||
TotalCount int
|
||||
}
|
||||
|
||||
type CollectResult interface {
|
||||
Analyze() []*analyze.AnalyzeResult
|
||||
IsRBACAllowed() bool
|
||||
@@ -36,7 +43,7 @@ func (cr ClusterCollectResult) IsRBACAllowed() bool {
|
||||
|
||||
type HostCollectResult struct {
|
||||
AllCollectedData map[string][]byte
|
||||
Collectors collect.HostCollectors
|
||||
Collectors []collect.HostCollector
|
||||
Spec *troubleshootv1beta2.HostPreflight
|
||||
}
|
||||
|
||||
@@ -53,12 +60,12 @@ func CollectHost(opts CollectOpts, p *troubleshootv1beta2.HostPreflight) (Collec
|
||||
|
||||
allCollectedData := make(map[string][]byte)
|
||||
|
||||
var collectors collect.HostCollectors
|
||||
var collectors []collect.HostCollector
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.HostCollector{
|
||||
Collect: desiredCollector,
|
||||
collector, ok := collect.GetHostCollector(desiredCollector)
|
||||
if ok {
|
||||
collectors = append(collectors, collector)
|
||||
}
|
||||
collectors = append(collectors, &collector)
|
||||
}
|
||||
|
||||
collectResult := HostCollectResult{
|
||||
@@ -67,9 +74,15 @@ func CollectHost(opts CollectOpts, p *troubleshootv1beta2.HostPreflight) (Collec
|
||||
}
|
||||
|
||||
for _, collector := range collectors {
|
||||
result, err := collector.RunCollectorSync()
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
continue
|
||||
}
|
||||
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Running collector...", collector.Title())
|
||||
result, err := collector.Collect(opts.ProgressChan)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector: %s: %v\n", collector.GetDisplayName(), err)
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector: %s: %v", collector.Title(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -128,22 +141,48 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, collector := range collectors {
|
||||
for i, collector := range collectors {
|
||||
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
|
||||
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "skipped",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "running",
|
||||
CompletedCount: i,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync(nil)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "failed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "completed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
for k, v := range result {
|
||||
allCollectedData[k] = v
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func LoadFromConfigMap(namespace string, configMapName string, key string) ([]byte, error) {
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert create k8s client")
|
||||
}
|
||||
|
||||
foundConfigMap, err := client.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get configmap")
|
||||
}
|
||||
|
||||
spec, ok := foundConfigMap.Data[key]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("spec not found in configmap %s", configMapName)
|
||||
}
|
||||
|
||||
return []byte(spec), nil
|
||||
}
|
||||
Reference in New Issue
Block a user