diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml index d35380708..44a7913e8 100644 --- a/.github/workflows/build-test-deploy.yaml +++ b/.github/workflows/build-test-deploy.yaml @@ -248,6 +248,12 @@ jobs: name: support-bundle path: bin/ - run: chmod +x bin/support-bundle + - name: Download preflight binary + uses: actions/download-artifact@v4 + with: + name: preflight + path: bin/ + - run: chmod +x bin/preflight - run: make support-bundle-e2e-go-test compile-collect: diff --git a/test/e2e/preflight/host_local_collector_e2e_test.go b/test/e2e/preflight/host_local_collector_e2e_test.go new file mode 100644 index 000000000..22d9cb734 --- /dev/null +++ b/test/e2e/preflight/host_local_collector_e2e_test.go @@ -0,0 +1,96 @@ +package e2e + +import ( + "bytes" + "context" + "fmt" + "os" + "os/exec" + "slices" + "strings" + "testing" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) + +func TestHostLocalCollector(t *testing.T) { + tests := []struct { + paths []string + notExpectedPaths []string + expectType string + }{ + { + paths: []string{ + "cpu.json", + }, + notExpectedPaths: []string{ + "node_list.json", + }, + expectType: "file", + }, + } + + feature := features.New("Preflight Host Local Collector"). + Assess("check preflight catch host local collector", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { + var out bytes.Buffer + var errOut bytes.Buffer + preflightName := "preflightbundle" + cmd := exec.CommandContext(ctx, preflightBinary(), "spec/localHostCollectors.yaml", "--interactive=false") + cmd.Stdout = &out + cmd.Stderr = &errOut + + err := cmd.Run() + tarPath := GetFilename(errOut.String(), preflightName) + if err != nil { + if tarPath == "" { + t.Error(err) + } + } + + defer func() { + err := os.Remove(tarPath) + if err != nil { + t.Error("Error remove file:", err) + } + }() + + targetFile := fmt.Sprintf("%s/host-collectors/system/", strings.TrimSuffix(tarPath, ".tar.gz")) + + files, _, err := readFilesAndFoldersFromTar(tarPath, targetFile) + if err != nil { + t.Error(err) + } + + for _, test := range tests { + if test.expectType == "file" { + for _, path := range test.paths { + if !slices.Contains(files, path) { + t.Errorf("Expected file %s not found in the tarball", path) + } + } + for _, path := range test.notExpectedPaths { + if slices.Contains(files, path) { + t.Errorf("Unexpected file %s found in the tarball", path) + } + } + } + } + return ctx + }).Feature() + testenv.Test(t, feature) +} + +func GetFilename(input, prefix string) string { + // Split the input into words + words := strings.Fields(input) + // Iterate over each word to find the one starting with the prefix + for _, word := range words { + if strings.HasPrefix(word, prefix) { + return word + } + } + + // Return an empty string if no match is found + return "" +} diff --git a/test/e2e/preflight/main_e2e_test.go b/test/e2e/preflight/main_e2e_test.go new file mode 100644 index 000000000..0a24853f1 --- /dev/null +++ b/test/e2e/preflight/main_e2e_test.go @@ -0,0 +1,146 @@ +package e2e + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "flag" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "k8s.io/klog/v2" + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/envfuncs" + "sigs.k8s.io/e2e-framework/support/kind" +) + +var testenv env.Environment + +const ClusterName = "kind-cluster" + +func TestMain(m *testing.M) { + // enable klog + klog.InitFlags(nil) + if os.Getenv("E2E_VERBOSE") == "1" { + _ = flag.Set("v", "10") + } + + testenv = env.New() + namespace := envconf.RandomName("default", 16) + testenv.Setup( + envfuncs.CreateCluster(kind.NewProvider(), ClusterName), + envfuncs.CreateNamespace(namespace), + ) + testenv.Finish( + envfuncs.DeleteNamespace(namespace), + envfuncs.DestroyCluster(ClusterName), + ) + os.Exit(testenv.Run(m)) +} + +func getClusterFromContext(t *testing.T, ctx context.Context, clusterName string) *kind.Cluster { + provider, ok := envfuncs.GetClusterFromContext(ctx, clusterName) + if !ok { + t.Fatalf("Failed to extract kind cluster %s from context", clusterName) + } + cluster, ok := provider.(*kind.Cluster) + if !ok { + t.Fatalf("Failed to cast kind cluster %s from provider", clusterName) + } + + return cluster +} + +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, err := filepath.Rel(targetFolder, header.Name) + if err != nil { + return nil, nil, fmt.Errorf("Error getting relative path: %w", err) + } + 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: %q", targetFile) +} + +func preflightBinary() string { + return "../../../bin/preflight" +} diff --git a/test/e2e/preflight/spec/localHostCollectors.yaml b/test/e2e/preflight/spec/localHostCollectors.yaml new file mode 100644 index 000000000..701a054b9 --- /dev/null +++ b/test/e2e/preflight/spec/localHostCollectors.yaml @@ -0,0 +1,19 @@ +apiVersion: troubleshoot.sh/v1beta2 +kind: HostPreflight +metadata: + name: ec-cluster-preflight +spec: + collectors: + - cpu: {} + analyzers: + - cpu: + checkName: "Number of CPUs" + outcomes: + - pass: + when: "count < 2" + message: At least 2 CPU cores are required, and 4 CPU cores are recommended + - warn: + when: "count < 4" + message: At least 4 CPU cores are recommended + - pass: + message: This server has at least 4 CPU cores diff --git a/test/e2e/support-bundle/host_local_collector_e2e_test.go b/test/e2e/support-bundle/host_local_collector_e2e_test.go new file mode 100644 index 000000000..01611761a --- /dev/null +++ b/test/e2e/support-bundle/host_local_collector_e2e_test.go @@ -0,0 +1,78 @@ +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 TestHostLocalCollector(t *testing.T) { + tests := []struct { + paths []string + notExpectedPaths []string + expectType string + }{ + { + paths: []string{ + "cpu.json", + "hostos_info.json", + "ipv4Interfaces.json", + "memory.json", + }, + notExpectedPaths: []string{ + "node_list.json", + }, + expectType: "file", + }, + } + feature := features.New("Host Local Collector"). + Assess("check support bundle catch host local collector", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { + var out bytes.Buffer + supportbundleName := "host-local-collector" + tarPath := fmt.Sprintf("%s.tar.gz", supportbundleName) + targetFile := fmt.Sprintf("%s/host-collectors/system/", supportbundleName) + cmd := exec.CommandContext(ctx, sbBinary(), "spec/localHostCollectors.yaml", "--interactive=false", fmt.Sprintf("-o=%s", supportbundleName)) + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + t.Error(err) + } + + defer func() { + err := os.Remove(fmt.Sprintf("%s.tar.gz", supportbundleName)) + if err != nil { + t.Error("Error remove file:", err) + } + }() + + files, _, err := readFilesAndFoldersFromTar(tarPath, targetFile) + if err != nil { + t.Error(err) + } + + for _, test := range tests { + if test.expectType == "file" { + for _, path := range test.notExpectedPaths { + if slices.Contains(files, path) { + t.Fatalf("Unexpected file %s found", path) + } + } + for _, path := range test.paths { + if !slices.Contains(files, path) { + t.Fatalf("Expected file %s not found", path) + } + } + } + } + + return ctx + }).Feature() + testenv.Test(t, feature) +} diff --git a/test/e2e/support-bundle/spec/localHostCollectors.yaml b/test/e2e/support-bundle/spec/localHostCollectors.yaml new file mode 100644 index 000000000..374fcd5b7 --- /dev/null +++ b/test/e2e/support-bundle/spec/localHostCollectors.yaml @@ -0,0 +1,33 @@ +apiVersion: troubleshoot.sh/v1beta2 +kind: SupportBundle +metadata: + name: "remote-host-collectors" +spec: + hostCollectors: + - ipv4Interfaces: {} + - hostServices: {} + - cpu: {} + - hostOS: {} + - memory: {} + - blockDevices: {} + - kernelConfigs: {} + - copy: + collectorName: etc-resolv + path: /etc/resolv.conf + - dns: + collectorName: replicated-app-resolve + hostnames: + - replicated.app + - diskUsage: + collectorName: root-disk-usage + path: / + - diskUsage: + collectorName: tmp + path: /tmp + - http: + collectorName: get-replicated-app + get: + url: https://replicated.app + - run: + collectorName: uptime + command: uptime