Dump debug info on e2e tests
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
@@ -88,6 +88,7 @@ metadata:
|
|||||||
log.Printf("failed to delete resources in '%s' namespace: %s", testID, err)
|
log.Printf("failed to delete resources in '%s' namespace: %s", testID, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
err := verifyGitAndKustomization(ctx, testEnv, testID, testID)
|
err := verifyGitAndKustomization(ctx, testEnv, testID, testID)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
func TestFluxInstallation(t *testing.T) {
|
func TestFluxInstallation(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, "flux-system") })
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
err := verifyGitAndKustomization(ctx, testEnv.Client, "flux-system", "flux-system")
|
err := verifyGitAndKustomization(ctx, testEnv.Client, "flux-system", "flux-system")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,6 +115,7 @@ metadata:
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
ctx := context.TODO()
|
||||||
ref := &sourcev1.GitRepositoryRef{
|
ref := &sourcev1.GitRepositoryRef{
|
||||||
Branch: branchName,
|
Branch: branchName,
|
||||||
}
|
}
|
||||||
@@ -143,6 +145,7 @@ metadata:
|
|||||||
t.Logf("failed to delete resources in '%s' namespace: %s", tt.name, err)
|
t.Logf("failed to delete resources in '%s' namespace: %s", tt.name, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)
|
err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ spec:
|
|||||||
t.Logf("failed to delete resources in '%s' namespace: %s", testID, err)
|
t.Logf("failed to delete resources in '%s' namespace: %s", testID, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)
|
err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ metadata:
|
|||||||
t.Logf("failed to delete resources in '%s' namespace: %s", testID, err)
|
t.Logf("failed to delete resources in '%s' namespace: %s", testID, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
g.Eventually(func() bool {
|
g.Eventually(func() bool {
|
||||||
err := verifyGitAndKustomization(ctx, testEnv, testID, testID)
|
err := verifyGitAndKustomization(ctx, testEnv, testID, testID)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ func TestOCIHelmRelease(t *testing.T) {
|
|||||||
}
|
}
|
||||||
g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed())
|
g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed())
|
||||||
defer testEnv.Delete(ctx, &namespace)
|
defer testEnv.Delete(ctx, &namespace)
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
repoURL := fmt.Sprintf("%s/charts/podinfo", cfg.testRegistry)
|
repoURL := fmt.Sprintf("%s/charts/podinfo", cfg.testRegistry)
|
||||||
err := pushImagesFromURL(repoURL, "ghcr.io/stefanprodan/charts/podinfo:6.2.0", []string{"6.2.0"})
|
err := pushImagesFromURL(repoURL, "ghcr.io/stefanprodan/charts/podinfo:6.2.0", []string{"6.2.0"})
|
||||||
@@ -97,23 +99,31 @@ func TestOCIHelmRelease(t *testing.T) {
|
|||||||
Namespace: helmRelease.Namespace,
|
Namespace: helmRelease.Namespace,
|
||||||
}
|
}
|
||||||
if err := testEnv.Get(ctx, nn, chart); err != nil {
|
if err := testEnv.Get(ctx, nn, chart); err != nil {
|
||||||
t.Logf("error getting helm chart %s\n", err.Error())
|
t.Logf("error getting helm chart: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if err := checkReadyCondition(chart); err != nil {
|
if err := checkReadyCondition(chart); err != nil {
|
||||||
t.Log(err)
|
t.Logf("HelmChart not ready: %s", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := &helmv2.HelmRelease{}
|
obj := &helmv2.HelmRelease{}
|
||||||
nn = types.NamespacedName{Name: helmRelease.Name, Namespace: helmRelease.Namespace}
|
nn = types.NamespacedName{Name: helmRelease.Name, Namespace: helmRelease.Namespace}
|
||||||
if err := testEnv.Get(ctx, nn, obj); err != nil {
|
if err := testEnv.Get(ctx, nn, obj); err != nil {
|
||||||
t.Logf("error getting helm release %s\n", err.Error())
|
t.Logf("error getting helm release: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkReadyCondition(obj); err != nil {
|
if err := checkReadyCondition(obj); err != nil {
|
||||||
t.Log(err)
|
// Log all HelmRelease conditions for full picture.
|
||||||
|
var condSummary []string
|
||||||
|
for _, c := range obj.Status.Conditions {
|
||||||
|
condSummary = append(condSummary, fmt.Sprintf("%s=%s (%s)", c.Type, c.Status, c.Message))
|
||||||
|
}
|
||||||
|
t.Logf("HelmRelease not ready: conditions=[%s]", strings.Join(condSummary, "; "))
|
||||||
|
|
||||||
|
// Log pod states in the release namespace.
|
||||||
|
logNamespacePods(t, ctx, testID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ stringData:
|
|||||||
log.Printf("failed to delete resources in '%s' namespace", testID)
|
log.Printf("failed to delete resources in '%s' namespace", testID)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })
|
||||||
|
|
||||||
if cfg.sopsSecretData != nil {
|
if cfg.sopsSecretData != nil {
|
||||||
secret := corev1.Secret{
|
secret := corev1.Secret{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
extgogit "github.com/go-git/go-git/v5"
|
extgogit "github.com/go-git/go-git/v5"
|
||||||
@@ -37,6 +38,9 @@ import (
|
|||||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
|
automationv1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||||
|
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
"github.com/fluxcd/pkg/git"
|
"github.com/fluxcd/pkg/git"
|
||||||
@@ -411,10 +415,164 @@ func checkReadyCondition(from conditions.Getter) error {
|
|||||||
if conditions.IsReady(from) {
|
if conditions.IsReady(from) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errMsg := fmt.Sprintf("object not ready")
|
errMsg := "object not ready"
|
||||||
readyMsg := conditions.GetMessage(from, meta.ReadyCondition)
|
readyMsg := conditions.GetMessage(from, meta.ReadyCondition)
|
||||||
if readyMsg != "" {
|
if readyMsg != "" {
|
||||||
errMsg += ": " + readyMsg
|
errMsg += ": " + readyMsg
|
||||||
}
|
}
|
||||||
return errors.New(errMsg)
|
return errors.New(errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dumpDiagnostics prints Flux object states and controller logs when a test
|
||||||
|
// has failed. It should be registered via t.Cleanup so that it runs after the
|
||||||
|
// test body completes.
|
||||||
|
func dumpDiagnostics(t *testing.T, ctx context.Context, namespace string) {
|
||||||
|
t.Helper()
|
||||||
|
if !t.Failed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("=== Diagnostics dump (test failed) ===")
|
||||||
|
dumpFluxObjects(t, ctx, namespace)
|
||||||
|
dumpControllerLogs(t, ctx)
|
||||||
|
t.Log("=== End diagnostics dump ===")
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpFluxObjects lists Flux custom resources in the given namespace and prints
|
||||||
|
// their status conditions.
|
||||||
|
func dumpFluxObjects(t *testing.T, ctx context.Context, namespace string) {
|
||||||
|
t.Helper()
|
||||||
|
listOpts := &client.ListOptions{Namespace: namespace}
|
||||||
|
|
||||||
|
gitRepos := &sourcev1.GitRepositoryList{}
|
||||||
|
if err := testEnv.List(ctx, gitRepos, listOpts); err == nil {
|
||||||
|
for _, r := range gitRepos.Items {
|
||||||
|
logObjectStatus(t, "GitRepository", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helmRepos := &sourcev1.HelmRepositoryList{}
|
||||||
|
if err := testEnv.List(ctx, helmRepos, listOpts); err == nil {
|
||||||
|
for _, r := range helmRepos.Items {
|
||||||
|
logObjectStatus(t, "HelmRepository", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helmCharts := &sourcev1.HelmChartList{}
|
||||||
|
if err := testEnv.List(ctx, helmCharts, listOpts); err == nil {
|
||||||
|
for _, r := range helmCharts.Items {
|
||||||
|
logObjectStatus(t, "HelmChart", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kustomizations := &kustomizev1.KustomizationList{}
|
||||||
|
if err := testEnv.List(ctx, kustomizations, listOpts); err == nil {
|
||||||
|
for _, r := range kustomizations.Items {
|
||||||
|
logObjectStatus(t, "Kustomization", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helmReleases := &helmv2.HelmReleaseList{}
|
||||||
|
if err := testEnv.List(ctx, helmReleases, listOpts); err == nil {
|
||||||
|
for _, r := range helmReleases.Items {
|
||||||
|
logObjectStatus(t, "HelmRelease", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageRepos := &reflectorv1.ImageRepositoryList{}
|
||||||
|
if err := testEnv.List(ctx, imageRepos, listOpts); err == nil {
|
||||||
|
for _, r := range imageRepos.Items {
|
||||||
|
logObjectStatus(t, "ImageRepository", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePolicies := &reflectorv1.ImagePolicyList{}
|
||||||
|
if err := testEnv.List(ctx, imagePolicies, listOpts); err == nil {
|
||||||
|
for _, r := range imagePolicies.Items {
|
||||||
|
logObjectStatus(t, "ImagePolicy", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageAutomations := &automationv1.ImageUpdateAutomationList{}
|
||||||
|
if err := testEnv.List(ctx, imageAutomations, listOpts); err == nil {
|
||||||
|
for _, r := range imageAutomations.Items {
|
||||||
|
logObjectStatus(t, "ImageUpdateAutomation", r.Name, r.Namespace, r.Status.Conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logObjectStatus prints the status conditions of a Flux object.
|
||||||
|
func logObjectStatus(t *testing.T, kind, name, namespace string, conditions []metav1.Condition) {
|
||||||
|
t.Helper()
|
||||||
|
t.Logf(" %s/%s (ns: %s):", kind, name, namespace)
|
||||||
|
for _, c := range conditions {
|
||||||
|
t.Logf(" %s: %s — %s (since %s)", c.Type, c.Status, c.Message, c.LastTransitionTime.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpControllerLogs prints the logs of all Flux controller pods in the
|
||||||
|
// flux-system namespace.
|
||||||
|
func dumpControllerLogs(t *testing.T, ctx context.Context) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
podList, err := testEnv.ClientGo.CoreV1().Pods("flux-system").List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed to list flux-system pods: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pod := range podList.Items {
|
||||||
|
logs, err := testEnv.ClientGo.
|
||||||
|
CoreV1().
|
||||||
|
Pods(pod.Namespace).
|
||||||
|
GetLogs(pod.Name, &corev1.PodLogOptions{}).
|
||||||
|
DoRaw(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed to get logs for pod %s: %v", pod.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Logf("=== Logs for pod %s ===\n%s", pod.Name, string(logs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logNamespacePods logs the state of all pods in the given namespace,
|
||||||
|
// including container statuses and recent events. Useful for understanding
|
||||||
|
// why a Helm install is stuck.
|
||||||
|
func logNamespacePods(t *testing.T, ctx context.Context, namespace string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
podList := &corev1.PodList{}
|
||||||
|
if err := testEnv.List(ctx, podList, &client.ListOptions{Namespace: namespace}); err != nil {
|
||||||
|
t.Logf(" failed to list pods in %s: %v", namespace, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(podList.Items) == 0 {
|
||||||
|
t.Logf(" no pods in namespace %s", namespace)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pod := range podList.Items {
|
||||||
|
t.Logf(" pod %s: phase=%s", pod.Name, pod.Status.Phase)
|
||||||
|
for _, cs := range pod.Status.ContainerStatuses {
|
||||||
|
if cs.State.Waiting != nil {
|
||||||
|
t.Logf(" container %s: waiting — %s: %s", cs.Name, cs.State.Waiting.Reason, cs.State.Waiting.Message)
|
||||||
|
} else if cs.State.Terminated != nil {
|
||||||
|
t.Logf(" container %s: terminated — %s (exit %d)", cs.Name, cs.State.Terminated.Reason, cs.State.Terminated.ExitCode)
|
||||||
|
} else if cs.State.Running != nil {
|
||||||
|
t.Logf(" container %s: running (ready=%v)", cs.Name, cs.Ready)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log recent events in the namespace for scheduling/pull failures.
|
||||||
|
events, err := testEnv.ClientGo.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(" failed to list events in %s: %v", namespace, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(events.Items) > 0 {
|
||||||
|
t.Logf(" events in namespace %s:", namespace)
|
||||||
|
for _, e := range events.Items {
|
||||||
|
t.Logf(" %s %s/%s: %s — %s", e.Type, e.InvolvedObject.Kind, e.InvolvedObject.Name, e.Reason, e.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user