From 3136069e5f1bc944442f998e9da71f188ec290c2 Mon Sep 17 00:00:00 2001 From: Danail Branekov Date: Tue, 25 Jul 2023 09:13:26 +0000 Subject: [PATCH] Add a fail handler to the smoke tests Also dry up all end to end tests making them reuse more bits like - fail handlers and common funcs used in them (like printing pod logs) - env helpers Co-authored-by: Georgi Sabev --- tests/crds/crds_suite_test.go | 18 +- tests/crds/crds_test.go | 7 +- tests/e2e/buildpacks_test.go | 2 +- tests/e2e/deployments_test.go | 2 +- tests/e2e/domains_test.go | 2 +- tests/e2e/e2e_suite_test.go | 171 ++++++++--- tests/e2e/helpers/fail_handler.go | 418 -------------------------- tests/e2e/matchers_test.go | 2 +- tests/e2e/orgs_test.go | 6 +- tests/e2e/processes_test.go | 2 +- tests/e2e/roles_test.go | 2 +- tests/e2e/routes_test.go | 6 +- tests/e2e/spaces_test.go | 2 +- tests/helpers/env.go | 24 ++ tests/helpers/fail_handler/handler.go | 47 +++ tests/helpers/fail_handler/pods.go | 182 +++++++++++ tests/{e2e => }/helpers/resty.go | 0 tests/smoke/smoke_suite_test.go | 97 +++++- tests/smoke/smoke_test.go | 15 +- 19 files changed, 492 insertions(+), 513 deletions(-) delete mode 100644 tests/e2e/helpers/fail_handler.go create mode 100644 tests/helpers/env.go create mode 100644 tests/helpers/fail_handler/handler.go create mode 100644 tests/helpers/fail_handler/pods.go rename tests/{e2e => }/helpers/resty.go (100%) diff --git a/tests/crds/crds_suite_test.go b/tests/crds/crds_suite_test.go index 3a54a2a00..12c656027 100644 --- a/tests/crds/crds_suite_test.go +++ b/tests/crds/crds_suite_test.go @@ -2,11 +2,11 @@ package crds_test import ( "fmt" - "os" "strings" "testing" "time" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/cloudfoundry/cf-test-helpers/cf" "github.com/cloudfoundry/cf-test-helpers/commandreporter" "github.com/cloudfoundry/cf-test-helpers/commandstarter" @@ -25,7 +25,7 @@ func TestCrds(t *testing.T) { var rootNamespace string var _ = BeforeSuite(func() { - rootNamespace = GetDefaultedEnvVar("ROOT_NAMESPACE", "cf") + rootNamespace = helpers.GetDefaultedEnvVar("ROOT_NAMESPACE", "cf") Eventually( kubectl("get", "namespace/"+rootNamespace), ).Should(Exit(0), "Could not find root namespace called %q", rootNamespace) @@ -54,20 +54,6 @@ func kubectlWithCustomReporter(cmdStarter *commandstarter.CommandStarter, report return request } -func GetRequiredEnvVar(envVarName string) string { - value, ok := os.LookupEnv(envVarName) - Expect(ok).To(BeTrue(), envVarName+" environment variable is required, but was not provided.") - return value -} - -func GetDefaultedEnvVar(envVarName, defaultValue string) string { - value, ok := os.LookupEnv(envVarName) - if !ok { - return defaultValue - } - return value -} - func loginAs(apiEndpoint string, skipSSL bool, user string) { apiArguments := []string{"api", apiEndpoint} if skipSSL { diff --git a/tests/crds/crds_test.go b/tests/crds/crds_test.go index 5e23567e0..28d0b499f 100644 --- a/tests/crds/crds_test.go +++ b/tests/crds/crds_test.go @@ -2,6 +2,7 @@ package crds_test import ( . "code.cloudfoundry.org/korifi/controllers/controllers/workloads/testutils" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/cloudfoundry/cf-test-helpers/cf" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -26,9 +27,9 @@ var _ = Describe("Using the k8s API directly", Ordered, func() { spaceGUID = PrefixedGUID("space") spaceDisplayName = PrefixedGUID("Space") - testCLIUser = GetRequiredEnvVar("CRDS_TEST_CLI_USER") - korifiAPIEndpoint = GetRequiredEnvVar("CRDS_TEST_API_ENDPOINT") - skipSSL = GetDefaultedEnvVar("CRDS_TEST_SKIP_SSL", "false") + testCLIUser = helpers.GetRequiredEnvVar("CRDS_TEST_CLI_USER") + korifiAPIEndpoint = helpers.GetRequiredEnvVar("CRDS_TEST_API_ENDPOINT") + skipSSL = helpers.GetDefaultedEnvVar("CRDS_TEST_SKIP_SSL", "false") cfUserRoleBindingName = testCLIUser + "-root-namespace-user" }) diff --git a/tests/e2e/buildpacks_test.go b/tests/e2e/buildpacks_test.go index 0444974fc..ee9619c9e 100644 --- a/tests/e2e/buildpacks_test.go +++ b/tests/e2e/buildpacks_test.go @@ -3,7 +3,7 @@ package e2e_test import ( "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" diff --git a/tests/e2e/deployments_test.go b/tests/e2e/deployments_test.go index 2bf6a3444..95c56e8ed 100644 --- a/tests/e2e/deployments_test.go +++ b/tests/e2e/deployments_test.go @@ -3,7 +3,7 @@ package e2e_test import ( "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/tests/e2e/domains_test.go b/tests/e2e/domains_test.go index ffee062c8..9a0916fa5 100644 --- a/tests/e2e/domains_test.go +++ b/tests/e2e/domains_test.go @@ -3,7 +3,7 @@ package e2e_test import ( "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go index c9a256227..f155bbb87 100644 --- a/tests/e2e/e2e_suite_test.go +++ b/tests/e2e/e2e_suite_test.go @@ -2,6 +2,7 @@ package e2e_test import ( "archive/zip" + "context" "crypto/tls" "encoding/json" "fmt" @@ -9,6 +10,7 @@ import ( "net/http" "os" "path/filepath" + "regexp" "strconv" "strings" "sync" @@ -16,14 +18,19 @@ import ( "time" "code.cloudfoundry.org/go-loggregator/v8/rpc/loggregator_v2" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" + "code.cloudfoundry.org/korifi/tests/helpers/fail_handler" "github.com/go-resty/resty/v2" "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/types" "gopkg.in/yaml.v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) @@ -269,12 +276,32 @@ type cfErr struct { Code int `json:"code"` } -func getCorrelationId() string { - return correlationId -} - func TestE2E(t *testing.T) { - RegisterFailHandler(helpers.E2EFailHandler(getCorrelationId)) + RegisterFailHandler(fail_handler.New("E2E Tests", map[types.GomegaMatcher]func(*rest.Config){ + fail_handler.Always: func(config *rest.Config) { + fail_handler.PrintPodsLogs(config, []fail_handler.PodContainerDescriptor{ + { + Namespace: systemNamespace(), + LabelKey: "app", + LabelValue: "korifi-api", + Container: "korifi-api", + CorrelationId: correlationId, + }, + { + Namespace: systemNamespace(), + LabelKey: "app", + LabelValue: "korifi-controllers", + Container: "manager", + }, + }) + }, + ContainSubstring("Droplet not found"): func(config *rest.Config) { + printDropletNotFoundDebugInfo(config) + }, + ContainSubstring("404"): func(config *rest.Config) { + printAllRoleBindings(config) + }, + })) RunSpecs(t, "E2E Suite") } @@ -302,7 +329,7 @@ var _ = SynchronizedBeforeSuite(func() []byte { // The DEFAULT_APP_BITS_PATH and DEFAULT_APP_RESPONSE environment variables are a workaround to allow e2e tests to run // with a different app in these environments. // See https://github.com/cloudfoundry/korifi/issues/2355 for refactoring ideas - DefaultAppBitsFile: zipAsset(getEnv("DEFAULT_APP_BITS_PATH", "../assets/dorifi")), + DefaultAppBitsFile: zipAsset(helpers.GetDefaultedEnvVar("DEFAULT_APP_BITS_PATH", "../assets/dorifi")), MultiProcessAppBitsFile: zipAsset("../assets/multi-process"), } @@ -344,24 +371,6 @@ var _ = BeforeEach(func() { correlationId = uuid.NewString() }) -func mustHaveEnv(key string) string { - GinkgoHelper() - - val, ok := os.LookupEnv(key) - Expect(ok).To(BeTrue(), "must set env var %q", key) - - return val -} - -func getEnv(key, defaultValue string) string { - value, ok := os.LookupEnv(key) - if !ok { - return defaultValue - } - - return value -} - func makeClient(certEnvVar, tokenEnvVar string) *helpers.CorrelatedRestyClient { GinkgoHelper() @@ -1077,23 +1086,23 @@ func expectUnprocessableEntityError(resp *resty.Response, errResp cfErrs, detail } func commonTestSetup() { - apiServerRoot = mustHaveEnv("API_SERVER_ROOT") - rootNamespace = mustHaveEnv("ROOT_NAMESPACE") - serviceAccountName = fmt.Sprintf("system:serviceaccount:%s:%s", rootNamespace, mustHaveEnv("E2E_SERVICE_ACCOUNT")) - serviceAccountToken = mustHaveEnv("E2E_SERVICE_ACCOUNT_TOKEN") - unprivilegedServiceAccountName = fmt.Sprintf("system:serviceaccount:%s:%s", rootNamespace, mustHaveEnv("E2E_UNPRIVILEGED_SERVICE_ACCOUNT")) - unprivilegedServiceAccountToken = mustHaveEnv("E2E_UNPRIVILEGED_SERVICE_ACCOUNT_TOKEN") - - certUserName = mustHaveEnv("E2E_USER_NAME") + apiServerRoot = helpers.GetRequiredEnvVar("API_SERVER_ROOT") + rootNamespace = helpers.GetRequiredEnvVar("ROOT_NAMESPACE") + serviceAccountName = fmt.Sprintf("system:serviceaccount:%s:%s", rootNamespace, helpers.GetRequiredEnvVar("E2E_SERVICE_ACCOUNT")) + serviceAccountToken = helpers.GetRequiredEnvVar("E2E_SERVICE_ACCOUNT_TOKEN") + unprivilegedServiceAccountName = fmt.Sprintf("system:serviceaccount:%s:%s", rootNamespace, helpers.GetRequiredEnvVar("E2E_UNPRIVILEGED_SERVICE_ACCOUNT")) + unprivilegedServiceAccountToken = helpers.GetRequiredEnvVar("E2E_UNPRIVILEGED_SERVICE_ACCOUNT_TOKEN") + + certUserName = helpers.GetRequiredEnvVar("E2E_USER_NAME") certPEM = os.Getenv("E2E_USER_PEM") - longCertUserName = mustHaveEnv("E2E_LONGCERT_USER_NAME") + longCertUserName = helpers.GetRequiredEnvVar("E2E_LONGCERT_USER_NAME") longCertPEM = os.Getenv("E2E_LONGCERT_USER_PEM") - appFQDN = mustHaveEnv("APP_FQDN") + appFQDN = helpers.GetRequiredEnvVar("APP_FQDN") - clusterVersionMinor, _ = strconv.Atoi(mustHaveEnv("CLUSTER_VERSION_MINOR")) - clusterVersionMajor, _ = strconv.Atoi(mustHaveEnv("CLUSTER_VERSION_MAJOR")) + clusterVersionMinor, _ = strconv.Atoi(helpers.GetRequiredEnvVar("CLUSTER_VERSION_MINOR")) + clusterVersionMajor, _ = strconv.Atoi(helpers.GetRequiredEnvVar("CLUSTER_VERSION_MAJOR")) ensureServerIsUp() @@ -1156,3 +1165,91 @@ func zipAsset(src string) string { return file.Name() } + +func systemNamespace() string { + systemNS, found := os.LookupEnv("SYSTEM_NAMESPACE") + if found { + return systemNS + } + + return "korifi" +} + +func getCorrelationId() string { + return correlationId +} + +func printDropletNotFoundDebugInfo(config *rest.Config) { + fmt.Fprint(GinkgoWriter, "\n\n========== Droplet not found debug log (start) ==========\n") + + fmt.Fprint(GinkgoWriter, "\n========== Kpack logs ==========\n") + fail_handler.PrintPodsLogs(config, []fail_handler.PodContainerDescriptor{ + { + Namespace: "kpack", + LabelKey: "app", + LabelValue: "kpack-controller", + }, + { + Namespace: "kpack", + LabelKey: "app", + LabelValue: "kpack-webhook", + }, + }) + + dropletGUID, err := getDropletGUID(CurrentSpecReport().FailureMessage()) + if err != nil { + fmt.Fprintf(GinkgoWriter, "Failed to get droplet GUID from message %v\n", err) + return + } + + fmt.Fprint(GinkgoWriter, "\n\n========== Droplet build logs ==========\n") + fmt.Fprintf(GinkgoWriter, "DropletGUID: %q\n", dropletGUID) + fail_handler.PrintPodsLogs(config, []fail_handler.PodContainerDescriptor{ + { + LabelKey: "korifi.cloudfoundry.org/build-workload-name", + LabelValue: dropletGUID, + }, + }) + fail_handler.PrintPodEvents(config, []fail_handler.PodContainerDescriptor{ + { + LabelKey: "korifi.cloudfoundry.org/build-workload-name", + LabelValue: dropletGUID, + }, + }) + + fmt.Fprint(GinkgoWriter, "\n\n========== Droplet not found debug log (end) ==========\n\n") +} + +func getDropletGUID(message string) (string, error) { + r := regexp.MustCompile(`Request.*droplets/(.*)`) + matches := r.FindStringSubmatch(message) + if len(matches) != 2 { + return "", fmt.Errorf("message does not match regex: %s", r.String()) + } + + return matches[1], nil +} + +func printAllRoleBindings(config *rest.Config) { + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Fprintf(GinkgoWriter, "failed to create clientset: %v\n", err) + return + } + + list, err := clientset.RbacV1().RoleBindings("").List(context.Background(), metav1.ListOptions{}) + if err != nil { + fmt.Printf("failed getting rolebindings: %v", err) + return + } + + fmt.Fprint(GinkgoWriter, "\n\n========== Expected 404 debug log ==========\n\n") + for _, b := range list.Items { + fmt.Fprintf(GinkgoWriter, "Name: %s, Namespace: %s, RoleKind: %s, RoleName: %s, Subjects: \n", + b.Name, b.Namespace, b.RoleRef.Kind, b.RoleRef.Name) + for _, s := range b.Subjects { + fmt.Fprintf(GinkgoWriter, "\tKind: %s, Name: %s, Namespace: %s\n", s.Kind, s.Name, s.Namespace) + } + } + fmt.Fprint(GinkgoWriter, "\n\n========== Expected 404 debug log (end) ==========\n\n") +} diff --git a/tests/e2e/helpers/fail_handler.go b/tests/e2e/helpers/fail_handler.go deleted file mode 100644 index 24b0687fd..000000000 --- a/tests/e2e/helpers/fail_handler.go +++ /dev/null @@ -1,418 +0,0 @@ -package helpers - -import ( - "bufio" - "bytes" - "context" - "fmt" - "os" - "regexp" - "strings" - - "code.cloudfoundry.org/korifi/tools" - "github.com/onsi/ginkgo/v2" - "gopkg.in/yaml.v3" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - controllerruntime "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type podContainerDescriptor struct { - Namespace string - LabelKey string - LabelValue string - Container string - CorrelationId string -} - -func E2EFailHandler(correlationId func() string) func(string, ...int) { - return func(message string, callerSkip ...int) { - fmt.Fprintf(ginkgo.GinkgoWriter, "Fail Handler: failure correlation ID: %q\n", correlationId()) - - if len(callerSkip) > 0 { - callerSkip[0] = callerSkip[0] + 2 - } else { - callerSkip = []int{2} - } - - defer func() { - fmt.Fprintln(ginkgo.GinkgoWriter, "Fail Handler: completed") - ginkgo.Fail(message, callerSkip...) - }() - - config, err := controllerruntime.GetConfig() - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "failed to get kubernetes config: %v\n", err) - return - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "failed to create clientset: %v\n", err) - return - } - - namespace := systemNamespace() - printPodsLogs(clientset, []podContainerDescriptor{ - { - Namespace: namespace, - LabelKey: "app", - LabelValue: "korifi-api", - Container: "korifi-api", - CorrelationId: correlationId(), - }, - { - Namespace: namespace, - LabelKey: "app", - LabelValue: "korifi-controllers", - Container: "manager", - }, - }) - - if strings.Contains(message, "Droplet not found") { - printDropletNotFoundDebugInfo(clientset, message) - } - - if strings.Contains(message, "404") { - printAllRoleBindings(clientset) - } - } -} - -func systemNamespace() string { - systemNS, found := os.LookupEnv("SYSTEM_NAMESPACE") - if found { - return systemNS - } - - return "korifi" -} - -func fullLogOnErr() bool { - return os.Getenv("FULL_LOG_ON_ERR") != "" -} - -func printPodsLogs(clientset kubernetes.Interface, podContainerDescriptors []podContainerDescriptor) { - for _, desc := range podContainerDescriptors { - pods, err := getPods(clientset, desc.Namespace, desc.LabelKey, desc.LabelValue) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get pods with label %s=%s: %v\n", desc.LabelKey, desc.LabelValue, err) - continue - } - - if len(pods) == 0 { - fmt.Fprintf(ginkgo.GinkgoWriter, "No pods with label %s=%s found\n", desc.LabelKey, desc.LabelValue) - continue - } - - for _, pod := range pods { - for _, container := range selectContainers(pod, desc.Container) { - printPodContainerLogs(clientset, pod, container, desc.CorrelationId) - } - } - } -} - -func selectContainers(pod corev1.Pod, container string) []string { - if container != "" { - return []string{container} - } - - result := []string{} - for _, initC := range pod.Spec.InitContainers { - result = append(result, initC.Name) - } - for _, c := range pod.Spec.Containers { - result = append(result, c.Name) - } - - return result -} - -func printPodContainerLogs(clientset kubernetes.Interface, pod corev1.Pod, container, correlationId string) { - log, err := getPodContainerLog(clientset, pod, container, correlationId) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get logs for pod %q, container %q: %v\n", pod.Name, container, err) - return - - } - if log == "" { - log = "No relevant logs found" - } - - logHeader := fmt.Sprintf( - "Logs for pod %q, container %q", - pod.Name, - container, - ) - if !fullLogOnErr() && correlationId != "" { - logHeader = fmt.Sprintf( - "Logs for pod %q, container %q with correlation ID %q", - pod.Name, - container, - correlationId, - ) - } - - fmt.Fprintf(ginkgo.GinkgoWriter, - "\n\n===== %s =====\n%s\n==============================================\n\n", - logHeader, - log) -} - -func getPods(clientset kubernetes.Interface, namespace, labelKey, labelValue string) ([]corev1.Pod, error) { - pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", labelKey, labelValue), - }) - if err != nil { - return nil, err - } - - return pods.Items, nil -} - -func getPodContainerLog(clientset kubernetes.Interface, pod corev1.Pod, container, correlationId string) (string, error) { - podLogOpts := corev1.PodLogOptions{ - SinceTime: tools.PtrTo(metav1.NewTime(ginkgo.CurrentSpecReport().StartTime)), - Container: container, - } - req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts) - - logStream, err := req.Stream(context.Background()) - if err != nil { - return "", err - } - defer logStream.Close() - - var logBuf bytes.Buffer - logScanner := bufio.NewScanner(logStream) - - for logScanner.Scan() { - if fullLogOnErr() || strings.Contains(logScanner.Text(), correlationId) { - logBuf.WriteString(logScanner.Text() + "\n") - } - } - - return logBuf.String(), logScanner.Err() -} - -func printPodEvents(clientset kubernetes.Interface, podContainerDescriptors []podContainerDescriptor) { - for _, desc := range podContainerDescriptors { - pods, err := getPods(clientset, desc.Namespace, desc.LabelKey, desc.LabelValue) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get pods with label %s=%s: %v\n", desc.LabelKey, desc.LabelValue, err) - continue - } - - if len(pods) == 0 { - fmt.Fprintf(ginkgo.GinkgoWriter, "No pods with label %s=%s found\n", desc.LabelKey, desc.LabelValue) - continue - } - - for _, pod := range pods { - printEvents(clientset, &pod) - } - } -} - -func printEvents(clientset kubernetes.Interface, obj client.Object) { - fmt.Fprintf(ginkgo.GinkgoWriter, "\n========== Events for %s %s/%s ==========\n", - obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName()) - events, err := clientset.CoreV1().Events(obj.GetNamespace()).List(context.Background(), metav1.ListOptions{ - FieldSelector: "involvedObject.name=" + obj.GetName(), - }) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get events: %v", err) - return - } - - fmt.Fprint(ginkgo.GinkgoWriter, "LAST SEEN\tTYPE\tREASON\tMESSAGE\n") - for _, event := range events.Items { - fmt.Fprintf(ginkgo.GinkgoWriter, "%s\t%s\t%s\t%s\n", event.LastTimestamp, event.Type, event.Reason, event.Message) - } -} - -func printDropletNotFoundDebugInfo(clientset kubernetes.Interface, message string) { - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== Droplet not found debug log (start) ==========\n") - - fmt.Fprint(ginkgo.GinkgoWriter, "\n========== Kpack logs ==========\n") - printPodsLogs(clientset, []podContainerDescriptor{ - { - Namespace: "kpack", - LabelKey: "app", - LabelValue: "kpack-controller", - }, - { - Namespace: "kpack", - LabelKey: "app", - LabelValue: "kpack-webhook", - }, - }) - - dropletGUID, err := getDropletGUID(message) - if err != nil { - fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get droplet GUID from message %v\n", err) - return - } - - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== Droplet build logs ==========\n") - fmt.Fprintf(ginkgo.GinkgoWriter, "DropletGUID: %q\n", dropletGUID) - printPodsLogs(clientset, []podContainerDescriptor{ - { - LabelKey: "korifi.cloudfoundry.org/build-workload-name", - LabelValue: dropletGUID, - }, - }) - printPodEvents(clientset, []podContainerDescriptor{ - { - LabelKey: "korifi.cloudfoundry.org/build-workload-name", - LabelValue: dropletGUID, - }, - }) - - if os.Getenv("CLUSTER_TYPE") == "EKS" { - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== EBS CSI plugin logs ==========\n") - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== ebs-csi-controller/ebs-plugin ==========\n") - printPodsLogs(clientset, []podContainerDescriptor{{ - Namespace: "kube-system", - LabelKey: "app", - LabelValue: "ebs-csi-controller", - Container: "ebs-plugin", - }}) - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== ebs-csi-controller/csi-provisioner ==========\n") - printPodsLogs(clientset, []podContainerDescriptor{{ - Namespace: "kube-system", - LabelKey: "app", - LabelValue: "ebs-csi-controller", - Container: "csi-provisioner", - }}) - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== ebs-csi-controller/csi-attacher ==========\n") - printPodsLogs(clientset, []podContainerDescriptor{{ - Namespace: "kube-system", - LabelKey: "app", - LabelValue: "ebs-csi-controller", - Container: "csi-attacher", - }}) - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== ebs-csi-node/ebs-plugin ==========\n") - printPodsLogs(clientset, []podContainerDescriptor{{ - Namespace: "kube-system", - LabelKey: "app", - LabelValue: "ebs-csi-node", - Container: "ebs-plugin", - }}) - - printPersistentVolumes(clientset) - printPersistentVolumeClaims(clientset) - printVolumeAttachments(clientset) - printWorkloadNamespaces(clientset) - } - - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== Droplet not found debug log (end) ==========\n\n") -} - -func getDropletGUID(message string) (string, error) { - r := regexp.MustCompile(`Request.*droplets/(.*)`) - matches := r.FindStringSubmatch(message) - if len(matches) != 2 { - return "", fmt.Errorf("message does not match regex: %s", r.String()) - } - - return matches[1], nil -} - -func printAllRoleBindings(clientset kubernetes.Interface) { - list, err := clientset.RbacV1().RoleBindings("").List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Printf("failed getting rolebindings: %v", err) - return - } - - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== Expected 404 debug log ==========\n\n") - for _, b := range list.Items { - fmt.Fprintf(ginkgo.GinkgoWriter, "Name: %s, Namespace: %s, RoleKind: %s, RoleName: %s, Subjects: \n", - b.Name, b.Namespace, b.RoleRef.Kind, b.RoleRef.Name) - for _, s := range b.Subjects { - fmt.Fprintf(ginkgo.GinkgoWriter, "\tKind: %s, Name: %s, Namespace: %s\n", s.Kind, s.Name, s.Namespace) - } - } - fmt.Fprint(ginkgo.GinkgoWriter, "\n\n========== Expected 404 debug log (end) ==========\n\n") -} - -func printPersistentVolumes(clientset kubernetes.Interface) { - pvList, err := clientset.CoreV1().PersistentVolumes().List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Printf("failed getting persistent volumes: %v", err) - return - } - - for _, pv := range pvList.Items { - fmt.Fprintf(ginkgo.GinkgoWriter, "\n\n========== PV %s/%s (skipping managed fields) ==========\n", pv.Namespace, pv.Name) - pv.ManagedFields = []metav1.ManagedFieldsEntry{} - pvBytes, err := yaml.Marshal(pv) - if err != nil { - fmt.Printf("failed marshalling persistent volume: %v", err) - return - } - fmt.Fprintln(ginkgo.GinkgoWriter, string(pvBytes)) - printEvents(clientset, &pv) - } -} - -func printPersistentVolumeClaims(clientset kubernetes.Interface) { - pvcList, err := clientset.CoreV1().PersistentVolumeClaims("").List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Printf("failed getting persistent volume claims: %v", err) - return - } - - for _, pvc := range pvcList.Items { - fmt.Fprintf(ginkgo.GinkgoWriter, "\n\n========== PVC %s/%s (skipping managed fields) ==========\n", pvc.Namespace, pvc.Name) - pvc.ManagedFields = []metav1.ManagedFieldsEntry{} - pvcBytes, err := yaml.Marshal(pvc) - if err != nil { - fmt.Printf("failed marshalling persistent volume claim: %v", err) - return - } - fmt.Fprintln(ginkgo.GinkgoWriter, string(pvcBytes)) - printEvents(clientset, &pvc) - } -} - -func printVolumeAttachments(clientset kubernetes.Interface) { - attachmentsList, err := clientset.StorageV1().VolumeAttachments().List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Printf("failed getting volume attachments: %v", err) - return - } - - for _, attachment := range attachmentsList.Items { - fmt.Fprintf(ginkgo.GinkgoWriter, "\n\n========== VolumeAttachment %s/%s (skipping managed fields) ==========\n", attachment.Namespace, attachment.Name) - attachment.ManagedFields = []metav1.ManagedFieldsEntry{} - attachmentBytes, err := yaml.Marshal(attachment) - if err != nil { - fmt.Printf("failed marshalling volume attachment: %v", err) - return - } - fmt.Fprintln(ginkgo.GinkgoWriter, string(attachmentBytes)) - printEvents(clientset, &attachment) - } -} - -func printWorkloadNamespaces(clientset kubernetes.Interface) { - nsList, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Printf("failed getting volume attachments: %v", err) - return - } - - fmt.Fprintln(ginkgo.GinkgoWriter, "\n\n========== Workload namespaces ==========") - for _, ns := range nsList.Items { - if !strings.HasPrefix(ns.Name, "cf") { - continue - } - - fmt.Fprintf(ginkgo.GinkgoWriter, "Name: %s, Status.Phase: %s", ns.Name, ns.Status.Phase) - } -} diff --git a/tests/e2e/matchers_test.go b/tests/e2e/matchers_test.go index 782407a6d..4e507aefc 100644 --- a/tests/e2e/matchers_test.go +++ b/tests/e2e/matchers_test.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" "github.com/onsi/gomega" "github.com/onsi/gomega/format" diff --git a/tests/e2e/orgs_test.go b/tests/e2e/orgs_test.go index 519012104..53d901ec8 100644 --- a/tests/e2e/orgs_test.go +++ b/tests/e2e/orgs_test.go @@ -10,7 +10,7 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" ) var _ = Describe("Orgs", func() { @@ -299,7 +299,7 @@ var _ = Describe("Orgs", func() { BeforeEach(func() { orgGUID = createOrg(generateGUID("org")) createOrgRole("organization_user", certUserName, orgGUID) - domainName = mustHaveEnv("APP_FQDN") + domainName = helpers.GetRequiredEnvVar("APP_FQDN") }) AfterEach(func() { @@ -367,7 +367,7 @@ var _ = Describe("Orgs", func() { It("succeeds", func() { Expect(resp).To(HaveRestyStatusCode(http.StatusOK)) - domainName := mustHaveEnv("APP_FQDN") + domainName := helpers.GetRequiredEnvVar("APP_FQDN") Expect(result.Name).To(Equal(domainName)) Expect(result.GUID).NotTo(BeEmpty()) }) diff --git a/tests/e2e/processes_test.go b/tests/e2e/processes_test.go index 037e2df3d..aa4010063 100644 --- a/tests/e2e/processes_test.go +++ b/tests/e2e/processes_test.go @@ -3,7 +3,7 @@ package e2e_test import ( "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" diff --git a/tests/e2e/roles_test.go b/tests/e2e/roles_test.go index aa976c048..632696299 100644 --- a/tests/e2e/roles_test.go +++ b/tests/e2e/roles_test.go @@ -3,7 +3,7 @@ package e2e_test import ( "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" diff --git a/tests/e2e/routes_test.go b/tests/e2e/routes_test.go index a96024514..aacad1a7b 100644 --- a/tests/e2e/routes_test.go +++ b/tests/e2e/routes_test.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" . "github.com/onsi/ginkgo/v2" @@ -26,7 +26,7 @@ var _ = Describe("Routes", func() { BeforeEach(func() { spaceGUID = createSpace(generateGUID("space"), commonTestOrgGUID) - domainName = mustHaveEnv("APP_FQDN") + domainName = helpers.GetRequiredEnvVar("APP_FQDN") domainGUID = getDomainGUID(domainName) host = generateGUID("myapp") @@ -379,7 +379,7 @@ var _ = Describe("Routes", func() { Expect(result.Destinations[0].App.GUID).To(Equal(appGUID)) // This enables replacing the default app output via DEFAULT_APP_BITS_PATH and DEFAULT_APP_RESPONSE - Expect(resp.Body()).To(ContainSubstring(getEnv("DEFAULT_APP_RESPONSE", "Hi, I'm Dorifi"))) + Expect(resp.Body()).To(ContainSubstring(helpers.GetDefaultedEnvVar("DEFAULT_APP_RESPONSE", "Hi, I'm Dorifi"))) }) When("an app from a different space is added", func() { diff --git a/tests/e2e/spaces_test.go b/tests/e2e/spaces_test.go index abcb6514b..b3b73aea5 100644 --- a/tests/e2e/spaces_test.go +++ b/tests/e2e/spaces_test.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "code.cloudfoundry.org/korifi/tests/e2e/helpers" + "code.cloudfoundry.org/korifi/tests/helpers" "github.com/go-resty/resty/v2" "github.com/google/uuid" diff --git a/tests/helpers/env.go b/tests/helpers/env.go new file mode 100644 index 000000000..ef5721b57 --- /dev/null +++ b/tests/helpers/env.go @@ -0,0 +1,24 @@ +package helpers + +import ( + "os" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +func GetRequiredEnvVar(envVarName string) string { + ginkgo.GinkgoHelper() + + value, ok := os.LookupEnv(envVarName) + gomega.Expect(ok).To(gomega.BeTrue(), envVarName+" environment variable is required, but was not provided.") + return value +} + +func GetDefaultedEnvVar(envVarName, defaultValue string) string { + value, ok := os.LookupEnv(envVarName) + if !ok { + return defaultValue + } + return value +} diff --git a/tests/helpers/fail_handler/handler.go b/tests/helpers/fail_handler/handler.go new file mode 100644 index 000000000..0e711b2b8 --- /dev/null +++ b/tests/helpers/fail_handler/handler.go @@ -0,0 +1,47 @@ +package fail_handler + +import ( + "fmt" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/types" + rest "k8s.io/client-go/rest" + controllerruntime "sigs.k8s.io/controller-runtime" +) + +var Always types.GomegaMatcher = gomega.ContainSubstring("") + +func New(name string, hooks map[types.GomegaMatcher]func(config *rest.Config)) func(message string, callerSkip ...int) { + config, err := controllerruntime.GetConfig() + if err != nil { + panic(err) + } + + return func(message string, callerSkip ...int) { + fmt.Fprintf(ginkgo.GinkgoWriter, "Fail Handler %s\n", name) + + if len(callerSkip) > 0 { + callerSkip[0] = callerSkip[0] + 2 + } else { + callerSkip = []int{2} + } + + defer func() { + fmt.Fprintf(ginkgo.GinkgoWriter, "Fail Handler %s: completed\n", name) + ginkgo.Fail(message, callerSkip...) + }() + + for matcher, hook := range hooks { + matchingMessage, err := matcher.Match(message) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to match message: %v\n", err) + return + } + + if matchingMessage { + hook(config) + } + } + } +} diff --git a/tests/helpers/fail_handler/pods.go b/tests/helpers/fail_handler/pods.go new file mode 100644 index 000000000..4a40f1592 --- /dev/null +++ b/tests/helpers/fail_handler/pods.go @@ -0,0 +1,182 @@ +package fail_handler + +import ( + "bufio" + "bytes" + "context" + "fmt" + "os" + "strings" + + "code.cloudfoundry.org/korifi/tools" + "github.com/onsi/ginkgo/v2" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + rest "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type PodContainerDescriptor struct { + Namespace string + LabelKey string + LabelValue string + Container string + CorrelationId string +} + +func PrintPodsLogs(config *rest.Config, podContainerDescriptors []PodContainerDescriptor) { + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "failed to create clientset: %v\n", err) + return + } + + for _, desc := range podContainerDescriptors { + pods, err := getPods(clientset, desc.Namespace, desc.LabelKey, desc.LabelValue) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get pods with label %s=%s: %v\n", desc.LabelKey, desc.LabelValue, err) + continue + } + + if len(pods) == 0 { + fmt.Fprintf(ginkgo.GinkgoWriter, "No pods with label %s=%s found\n", desc.LabelKey, desc.LabelValue) + continue + } + + for _, pod := range pods { + for _, container := range selectContainers(pod, desc.Container) { + printPodContainerLogs(clientset, pod, container, desc.CorrelationId) + } + } + } +} + +func PrintPodEvents(config *rest.Config, podContainerDescriptors []PodContainerDescriptor) { + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "failed to create clientset: %v\n", err) + return + } + + for _, desc := range podContainerDescriptors { + pods, err := getPods(clientset, desc.Namespace, desc.LabelKey, desc.LabelValue) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get pods with label %s=%s: %v\n", desc.LabelKey, desc.LabelValue, err) + continue + } + + if len(pods) == 0 { + fmt.Fprintf(ginkgo.GinkgoWriter, "No pods with label %s=%s found\n", desc.LabelKey, desc.LabelValue) + continue + } + + for _, pod := range pods { + printEvents(clientset, &pod) + } + } +} + +func printEvents(clientset kubernetes.Interface, obj client.Object) { + fmt.Fprintf(ginkgo.GinkgoWriter, "\n========== Events for %s %s/%s ==========\n", + obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName()) + events, err := clientset.CoreV1().Events(obj.GetNamespace()).List(context.Background(), metav1.ListOptions{ + FieldSelector: "involvedObject.name=" + obj.GetName(), + }) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get events: %v", err) + return + } + + fmt.Fprint(ginkgo.GinkgoWriter, "LAST SEEN\tTYPE\tREASON\tMESSAGE\n") + for _, event := range events.Items { + fmt.Fprintf(ginkgo.GinkgoWriter, "%s\t%s\t%s\t%s\n", event.LastTimestamp, event.Type, event.Reason, event.Message) + } +} + +func getPods(clientset kubernetes.Interface, namespace, labelKey, labelValue string) ([]corev1.Pod, error) { + pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", labelKey, labelValue), + }) + if err != nil { + return nil, err + } + + return pods.Items, nil +} + +func selectContainers(pod corev1.Pod, container string) []string { + if container != "" { + return []string{container} + } + + result := []string{} + for _, initC := range pod.Spec.InitContainers { + result = append(result, initC.Name) + } + for _, c := range pod.Spec.Containers { + result = append(result, c.Name) + } + + return result +} + +func printPodContainerLogs(clientset kubernetes.Interface, pod corev1.Pod, container, correlationId string) { + log, err := getPodContainerLog(clientset, pod, container, correlationId) + if err != nil { + fmt.Fprintf(ginkgo.GinkgoWriter, "Failed to get logs for pod %q, container %q: %v\n", pod.Name, container, err) + return + + } + if log == "" { + log = "No relevant logs found" + } + + logHeader := fmt.Sprintf( + "Logs for pod %q, container %q", + pod.Name, + container, + ) + if !fullLogOnErr() && correlationId != "" { + logHeader = fmt.Sprintf( + "Logs for pod %q, container %q with correlation ID %q", + pod.Name, + container, + correlationId, + ) + } + + fmt.Fprintf(ginkgo.GinkgoWriter, + "\n\n===== %s =====\n%s\n==============================================\n\n", + logHeader, + log) +} + +func getPodContainerLog(clientset kubernetes.Interface, pod corev1.Pod, container, correlationId string) (string, error) { + podLogOpts := corev1.PodLogOptions{ + SinceTime: tools.PtrTo(metav1.NewTime(ginkgo.CurrentSpecReport().StartTime)), + Container: container, + } + req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts) + + logStream, err := req.Stream(context.Background()) + if err != nil { + return "", err + } + defer logStream.Close() + + var logBuf bytes.Buffer + logScanner := bufio.NewScanner(logStream) + + for logScanner.Scan() { + if fullLogOnErr() || strings.Contains(logScanner.Text(), correlationId) { + logBuf.WriteString(logScanner.Text() + "\n") + } + } + + return logBuf.String(), logScanner.Err() +} + +func fullLogOnErr() bool { + return os.Getenv("FULL_LOG_ON_ERR") != "" +} diff --git a/tests/e2e/helpers/resty.go b/tests/helpers/resty.go similarity index 100% rename from tests/e2e/helpers/resty.go rename to tests/helpers/resty.go diff --git a/tests/smoke/smoke_suite_test.go b/tests/smoke/smoke_suite_test.go index 6081c8e4a..b9005269f 100644 --- a/tests/smoke/smoke_suite_test.go +++ b/tests/smoke/smoke_suite_test.go @@ -1,16 +1,29 @@ package smoke_test import ( + "context" + "fmt" "os" + "strings" "testing" "time" + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/tests/helpers" + "code.cloudfoundry.org/korifi/tests/helpers/fail_handler" "github.com/cloudfoundry/cf-test-helpers/cf" "github.com/cloudfoundry/cf-test-helpers/generator" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" + gomegatypes "github.com/onsi/gomega/types" + "gopkg.in/yaml.v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" + rest "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" ) const NamePrefix = "cf-on-k8s-smoke" @@ -24,13 +37,27 @@ var ( ) func TestSmoke(t *testing.T) { - RegisterFailHandler(Fail) - SetDefaultEventuallyTimeout(10 * time.Minute) + RegisterFailHandler(fail_handler.New("Smoke Tests", map[gomegatypes.GomegaMatcher]func(*rest.Config){ + fail_handler.Always: func(config *rest.Config) { + _, _ = runCfCmd("apps") + printCfApp(config) + fail_handler.PrintPodsLogs(config, []fail_handler.PodContainerDescriptor{ + { + Namespace: "korifi", + LabelKey: "app", + LabelValue: "korifi-controllers", + Container: "manager", + }, + }) + }, + })) + SetDefaultEventuallyTimeout(5 * time.Minute) + SetDefaultEventuallyPollingInterval(5 * time.Second) RunSpecs(t, "Smoke Tests Suite") } var _ = BeforeSuite(func() { - apiArguments := []string{"api", GetRequiredEnvVar("SMOKE_TEST_API_ENDPOINT")} + apiArguments := []string{"api", helpers.GetRequiredEnvVar("SMOKE_TEST_API_ENDPOINT")} skipSSL := os.Getenv("SMOKE_TEST_SKIP_SSL") == "true" if skipSSL { apiArguments = append(apiArguments, "--skip-ssl-validation") @@ -38,10 +65,10 @@ var _ = BeforeSuite(func() { Eventually(cf.Cf(apiArguments...)).Should(Exit(0)) - loginAs(GetRequiredEnvVar("SMOKE_TEST_USER")) + loginAs(helpers.GetRequiredEnvVar("SMOKE_TEST_USER")) - appRouteProtocol = GetDefaultedEnvVar("SMOKE_TEST_APP_ROUTE_PROTOCOL", "https") - appsDomain = GetRequiredEnvVar("SMOKE_TEST_APPS_DOMAIN") + appRouteProtocol = helpers.GetDefaultedEnvVar("SMOKE_TEST_APP_ROUTE_PROTOCOL", "https") + appsDomain = helpers.GetRequiredEnvVar("SMOKE_TEST_APPS_DOMAIN") orgName = generator.PrefixedRandomName(NamePrefix, "org") spaceName = generator.PrefixedRandomName(NamePrefix, "space") appName = generator.PrefixedRandomName(NamePrefix, "app") @@ -63,20 +90,58 @@ var _ = AfterSuite(func() { if orgName != "" { Eventually(func() *Session { return cf.Cf("delete-org", orgName, "-f").Wait() - }, 2*time.Minute, 1*time.Second).Should(Exit(0)) + }).Should(Exit(0)) } }) -func GetRequiredEnvVar(envVarName string) string { - value, ok := os.LookupEnv(envVarName) - Expect(ok).To(BeTrue(), envVarName+" environment variable is required, but was not provided.") - return value +func runCfCmd(args ...string) (string, error) { + session := cf.Cf(args...) + <-session.Exited + if session.ExitCode() != 0 { + return "", fmt.Errorf("cf %s exited with code %d", strings.Join(args, " "), session.ExitCode()) + } + + return string(session.Out.Contents()), nil } -func GetDefaultedEnvVar(envVarName, defaultValue string) string { - value, ok := os.LookupEnv(envVarName) - if !ok { - return defaultValue +func printCfApp(config *rest.Config) { + utilruntime.Must(korifiv1alpha1.AddToScheme(scheme.Scheme)) + k8sClient, err := client.New(config, client.Options{Scheme: scheme.Scheme}) + if err != nil { + fmt.Fprintf(GinkgoWriter, "failed to create k8s client: %v\n", err) + return + } + + cfAppNamespace, err := runCfCmd("space", spaceName, "--guid") + if err != nil { + fmt.Fprintf(GinkgoWriter, "failed to get app space guid: %v\n", err) + return + } + + cfAppGUID, err := runCfCmd("app", appName, "--guid") + if err != nil { + fmt.Fprintf(GinkgoWriter, "failed to get app guid: %v\n", err) + return + } + + cfApp := &korifiv1alpha1.CFApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: cfAppGUID, + Namespace: cfAppNamespace, + }, + } + + if err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(cfApp), cfApp); err != nil { + fmt.Fprintf(GinkgoWriter, "failed to get cfapp in namespace %q: %v\n", cfAppNamespace, err) + return + } + + fmt.Fprintf(GinkgoWriter, "\n\n========== cfapp %s/%s (skipping managed fields) ==========\n", cfApp.Namespace, cfApp.Name) + cfApp.ManagedFields = []metav1.ManagedFieldsEntry{} + cfAppBytes, err := yaml.Marshal(cfApp) + if err != nil { + fmt.Fprintf(GinkgoWriter, "failed marshalling cfapp: %v\n", err) + return } - return value + fmt.Fprintln(GinkgoWriter, string(cfAppBytes)) } diff --git a/tests/smoke/smoke_test.go b/tests/smoke/smoke_test.go index 2edc974c6..e75c8ce70 100644 --- a/tests/smoke/smoke_test.go +++ b/tests/smoke/smoke_test.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" "strings" - "time" . "code.cloudfoundry.org/korifi/tests/matchers" "github.com/cloudfoundry/cf-test-helpers/cf" @@ -19,8 +18,8 @@ import ( ) var _ = Describe("Smoke Tests", func() { - Describe("cf push", func() { - It("runs the app", func() { + Describe("pushed app", func() { + It("is reachable via its route", func() { appResponseShould("/", SatisfyAll( HaveHTTPStatus(http.StatusOK), HaveHTTPBody(ContainSubstring("Hi, I'm Dorifi!")), @@ -30,17 +29,13 @@ var _ = Describe("Smoke Tests", func() { Describe("cf logs", func() { It("prints app logs", func() { - Eventually(func(g Gomega) { - cfLogs := cf.Cf("logs", appName, "--recent") - g.Expect(cfLogs.Wait().Out).To(gbytes.Say("Listening on port 8080")) - }, 2*time.Minute, 2*time.Second).Should(Succeed()) + Eventually(cf.Cf("logs", appName, "--recent")).Should(gbytes.Say("Listening on port 8080")) }) }) Describe("cf run-task", func() { It("succeeds", func() { - cfRunTask := cf.Cf("run-task", appName, "-c", `echo "Hello from the task"`) - Eventually(cfRunTask).Should(Exit(0)) + Eventually(cf.Cf("run-task", appName, "-c", `echo "Hello from the task"`)).Should(Exit(0)) }) }) @@ -104,5 +99,5 @@ func appResponseShould(requestPath string, matchExpectations types.GomegaMatcher resp, err := httpClient.Get(fmt.Sprintf("%s://%s.%s%s", appRouteProtocol, appName, appsDomain, requestPath)) g.Expect(err).NotTo(HaveOccurred()) g.Expect(resp).To(matchExpectations) - }, 5*time.Minute, 30*time.Second).Should(Succeed()) + }).Should(Succeed()) }