mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
feat(e2e): add e2e go test for support-bundler (#1265)
This commit is contained in:
21
.github/workflows/build-test-deploy.yaml
vendored
21
.github/workflows/build-test-deploy.yaml
vendored
@@ -222,6 +222,27 @@ jobs:
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-test
|
||||
|
||||
validate-supportbundle-e2e-go-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-go-test
|
||||
|
||||
compile-collect:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
9
Makefile
9
Makefile
@@ -36,6 +36,7 @@ endef
|
||||
|
||||
BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" -installsuffix netgo
|
||||
BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
|
||||
E2EPATHS = ./test/e2e/...
|
||||
TESTFLAGS ?= -v -coverprofile cover.out
|
||||
|
||||
.DEFAULT: all
|
||||
@@ -71,6 +72,14 @@ run-examples:
|
||||
support-bundle-e2e-test:
|
||||
./test/validate-support-bundle-e2e.sh
|
||||
|
||||
.PHONY: support-bundle-e2e-go-test
|
||||
support-bundle-e2e-go-test:
|
||||
if [ -n $(RUN) ]; then \
|
||||
go test ${BUILDFLAGS} ${E2EPATHS} -v -run $(RUN); \
|
||||
else \
|
||||
go test ${BUILDFLAGS} ${E2EPATHS} -v; \
|
||||
fi
|
||||
|
||||
# Build all binaries in parallel ( -j )
|
||||
build:
|
||||
@echo "Build cli binaries"
|
||||
|
||||
2
go.mod
2
go.mod
@@ -72,6 +72,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.1 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
github.com/vladimirvivien/gexe v0.2.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
@@ -79,6 +80,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
sigs.k8s.io/e2e-framework v0.2.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
4
go.sum
4
go.sum
@@ -810,6 +810,8 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY=
|
||||
github.com/vladimirvivien/gexe v0.2.0/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
@@ -1490,6 +1492,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c=
|
||||
sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
|
||||
sigs.k8s.io/e2e-framework v0.2.0 h1:gD6AWWAHFcHibI69E9TgkNFhh0mVwWtRCHy2RU057jQ=
|
||||
sigs.k8s.io/e2e-framework v0.2.0/go.mod h1:E6JXj/V4PIlb95jsn2WrNKG+Shb45xaaI7C0+BH4PL8=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA=
|
||||
|
||||
95
test/e2e/support-bundle/cluster_pod_statuses_e2e_test.go
Normal file
95
test/e2e/support-bundle/cluster_pod_statuses_e2e_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/e2e-framework/pkg/envconf"
|
||||
"sigs.k8s.io/e2e-framework/pkg/features"
|
||||
)
|
||||
|
||||
func TestPendingPod(t *testing.T) {
|
||||
supportBundleName := "pod-deployment"
|
||||
deploymentName := "test-pending-deployment"
|
||||
containerName := "curl"
|
||||
feature := features.New("Pending Pod Test").
|
||||
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
deployment := newDeployment(c.Namespace(), deploymentName, 1, containerName)
|
||||
client, err := c.NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = client.Resources().Create(ctx, deployment); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}).
|
||||
Assess("check support bundle catch pending pod", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
var out bytes.Buffer
|
||||
var results []*convert.Result
|
||||
|
||||
tarPath := fmt.Sprintf("%s.tar.gz", supportBundleName)
|
||||
targetFile := fmt.Sprintf("%s/analysis.json", supportBundleName)
|
||||
|
||||
cmd := exec.Command("../../../bin/support-bundle", "spec/pod.yaml", "--interactive=false", fmt.Sprintf("-o=%s", supportBundleName))
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
analysisJSON, err := readFileFromTar(tarPath, targetFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(analysisJSON, &results)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
if strings.Contains(result.Insight.Detail, deploymentName) {
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatal("Pending pod not found")
|
||||
defer func() {
|
||||
err := os.Remove(fmt.Sprintf("%s.tar.gz", supportBundleName))
|
||||
if err != nil {
|
||||
t.Fatal("Error remove file:", err)
|
||||
}
|
||||
}()
|
||||
return ctx
|
||||
}).Feature()
|
||||
testenv.Test(t, feature)
|
||||
}
|
||||
|
||||
func newDeployment(namespace string, name string, replicas int32, containerName string) *appsv1.Deployment {
|
||||
labels := map[string]string{"app": "pending-test"}
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labels},
|
||||
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: containerName, Image: "nginx", Command: []string{"wge", "-O", "/work-dir/index.html", "https://www.wikipedia.org"}}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
111
test/e2e/support-bundle/cluster_resources_e2e_test.go
Normal file
111
test/e2e/support-bundle/cluster_resources_e2e_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"sigs.k8s.io/e2e-framework/pkg/envconf"
|
||||
"sigs.k8s.io/e2e-framework/pkg/features"
|
||||
)
|
||||
|
||||
func TestClusterResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
paths []string
|
||||
expectType string
|
||||
}{
|
||||
{
|
||||
paths: []string{
|
||||
"clusterroles.json",
|
||||
"volumeattachments.json",
|
||||
"nodes.json",
|
||||
"pvs.json",
|
||||
"pod-disruption-budgets-errors.json",
|
||||
"resources.json",
|
||||
"cronjobs-errors.json",
|
||||
"custom-resource-definitions.json",
|
||||
"groups.json",
|
||||
"priorityclasses.json",
|
||||
"namespaces.json",
|
||||
"clusterrolebindings.json",
|
||||
"storage-classes.json",
|
||||
},
|
||||
expectType: "file",
|
||||
},
|
||||
{
|
||||
paths: []string{
|
||||
"limitranges",
|
||||
"daemonsets",
|
||||
"deployments",
|
||||
"pvcs",
|
||||
"leases",
|
||||
"auth-cani-list",
|
||||
"services",
|
||||
"roles",
|
||||
"events",
|
||||
"rolebindings",
|
||||
"statefulsets-errors.json",
|
||||
"jobs",
|
||||
"serviceaccounts",
|
||||
"configmaps",
|
||||
"statefulsets",
|
||||
"endpoints",
|
||||
"network-policy",
|
||||
"resource-quota",
|
||||
"ingress",
|
||||
"pods",
|
||||
},
|
||||
expectType: "folder",
|
||||
},
|
||||
}
|
||||
|
||||
feature := features.New("Cluster Resouces Test").
|
||||
Assess("check support bundle catch cluster resouces", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
var out bytes.Buffer
|
||||
supportBundleName := "cluster-resources"
|
||||
tarPath := fmt.Sprintf("%s.tar.gz", supportBundleName)
|
||||
targetFolder := fmt.Sprintf("%s/cluster-resources/", supportBundleName)
|
||||
cmd := exec.Command("../../../bin/support-bundle", "spec/clusterResources.yaml", "--interactive=false", fmt.Sprintf("-o=%s", supportBundleName))
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := os.Remove(fmt.Sprintf("%s.tar.gz", supportBundleName))
|
||||
if err != nil {
|
||||
t.Fatal("Error remove file:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
files, folders, err := readFilesAndFoldersFromTar(tarPath, targetFolder)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if test.expectType == "file" {
|
||||
for _, path := range test.paths {
|
||||
if !slices.Contains(files, path) {
|
||||
t.Fatalf("Expected file %s not found", path)
|
||||
}
|
||||
}
|
||||
} else if test.expectType == "folder" {
|
||||
for _, path := range test.paths {
|
||||
if !slices.Contains(folders, path) {
|
||||
t.Fatalf("Expected folder %s not found", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}).Feature()
|
||||
testenv.Test(t, feature)
|
||||
}
|
||||
115
test/e2e/support-bundle/main_e2e_test.go
Normal file
115
test/e2e/support-bundle/main_e2e_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/e2e-framework/pkg/env"
|
||||
"sigs.k8s.io/e2e-framework/pkg/envconf"
|
||||
"sigs.k8s.io/e2e-framework/pkg/envfuncs"
|
||||
)
|
||||
|
||||
var testenv env.Environment
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testenv = env.New()
|
||||
kindClusterName := envconf.RandomName("e2e-cluster", 16)
|
||||
namespace := envconf.RandomName("default", 16)
|
||||
testenv.Setup(
|
||||
envfuncs.CreateKindCluster(kindClusterName),
|
||||
envfuncs.CreateNamespace(namespace),
|
||||
)
|
||||
testenv.Finish(
|
||||
envfuncs.DeleteNamespace(namespace),
|
||||
envfuncs.DestroyKindCluster(kindClusterName),
|
||||
)
|
||||
os.Exit(testenv.Run(m))
|
||||
}
|
||||
|
||||
func readFilesAndFoldersFromTar(tarPath, targetFolder string) ([]string, []string, error) {
|
||||
file, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error opening file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzipReader, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error initializing gzip reader: %w", err)
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzipReader)
|
||||
var files []string
|
||||
var folders []string
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error reading tar: %w", err)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(header.Name, targetFolder) {
|
||||
relativePath := strings.TrimPrefix(header.Name, targetFolder)
|
||||
if relativePath != "" {
|
||||
relativeDir := filepath.Dir(relativePath)
|
||||
if relativeDir != "." {
|
||||
parentDir := strings.Split(relativeDir, "/")[0]
|
||||
folders = append(folders, parentDir)
|
||||
} else {
|
||||
files = append(files, relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files, folders, nil
|
||||
}
|
||||
|
||||
func readFileFromTar(tarPath, targetFile string) ([]byte, error) {
|
||||
file, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error opening file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzipReader, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error initializing gzip reader: %w", err)
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzipReader)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading tar: %w", err)
|
||||
}
|
||||
|
||||
if header.Name == targetFile {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.Copy(buf, tarReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error copying data: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("File not found: %s", targetFile)
|
||||
}
|
||||
7
test/e2e/support-bundle/spec/clusterResources.yaml
Normal file
7
test/e2e/support-bundle/spec/clusterResources.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sample
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}
|
||||
24
test/e2e/support-bundle/spec/pod.yaml
Normal file
24
test/e2e/support-bundle/spec/pod.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sample
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}
|
||||
- clusterInfo:
|
||||
exclude: false
|
||||
analyzers:
|
||||
- clusterPodStatuses:
|
||||
checkName: "Pod(s) health status(es)"
|
||||
outcomes:
|
||||
- fail:
|
||||
title: "Pod {{ .Name }} is unable to pull images"
|
||||
when: "== ImagePullBackOff"
|
||||
message: "A Pod, {{ .Name }}, is unable to pull its image. Status is: {{ .Status.Reason }}. Message is: {{ .Status.Message }}"
|
||||
- warn:
|
||||
title: "Pod {{ .Name }} is unhealthy"
|
||||
when: "!= Healthy"
|
||||
message: "A Pod, {{ .Name }}, is unhealthy with a status of: {{ .Status.Reason }}. Message is: {{ .Status.Message }}"
|
||||
- pass:
|
||||
title: "Pod {{ .Name }} is healthy"
|
||||
message: "Pod {{ .Name }} is healthy"
|
||||
@@ -38,36 +38,6 @@ if grep -q "No matching files" "$tmpdir/$bundle_directory_name/analysis.json"; t
|
||||
exit 1
|
||||
fi
|
||||
|
||||
base_path="$tmpdir/$bundle_directory_name/cluster-resources"
|
||||
folders=("auth-cani-list" "configmaps" "daemonsets" "endpoints" "events" "deployments" "leases" "services" "pvcs" "pvcs" "jobs" "roles" "statefulsets" "network-policy" "pods" "resource-quota" "rolebindings" "serviceaccounts")
|
||||
|
||||
files=("namespaces" "volumeattachments" "pvs" "groups" "nodes" "priorityclasses" "resources")
|
||||
|
||||
for folder in "${folders[@]}"; do
|
||||
if [ -d "$base_path/$folder" ]; then
|
||||
echo "$folder directory was collected"
|
||||
if [ "$(ls -A $base_path/$folder)" ]; then
|
||||
echo "$folder directory is not empty"
|
||||
else
|
||||
echo "$folder directory is empty"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "The $folder folder does not exist in $base_path path."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
if [ -e "$base_path/$file.json" ]
|
||||
then
|
||||
echo "$file.json file was collected"
|
||||
else
|
||||
echo "The $file.json file does not exist in $base_path path."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
EXIT_STATUS=0
|
||||
jq -r '.[].insight.severity' "$tmpdir/$bundle_directory_name/analysis.json" | while read i; do
|
||||
if [ $i == "error" ]; then
|
||||
|
||||
Reference in New Issue
Block a user