|
8 | 8 | "io" |
9 | 9 | "log" |
10 | 10 | "os" |
| 11 | + "os/exec" |
| 12 | + "path/filepath" |
11 | 13 | "regexp" |
12 | 14 | "strconv" |
13 | 15 | "strings" |
@@ -123,6 +125,88 @@ func (f *Framework) cleanUpFromYAMLFile(p *string) error { |
123 | 125 | return nil |
124 | 126 | } |
125 | 127 |
|
| 128 | +// CollectMustGather collects must-gather data from the cluster for debugging test failures. |
| 129 | +// It attempts to determine the must-gather image from the CSV and runs oc adm must-gather. |
| 130 | +// If the CSV cannot be found or the must-gather image is not available, it falls back to |
| 131 | +// using a default image or skips collection. |
| 132 | +// The output is saved to ARTIFACT_DIR if set, otherwise to the current directory. |
| 133 | +func (f *Framework) CollectMustGather(testName string) { |
| 134 | + timestamp := time.Now().Format("20060102-150405") |
| 135 | + outputDirName := fmt.Sprintf("must-gather-%s-%s", testName, timestamp) |
| 136 | + |
| 137 | + // Use ARTIFACT_DIR if set (for CI), otherwise use current directory |
| 138 | + baseDir := os.Getenv("ARTIFACT_DIR") |
| 139 | + var outputDir string |
| 140 | + if baseDir != "" { |
| 141 | + outputDir = filepath.Join(baseDir, outputDirName) |
| 142 | + log.Printf("Collecting must-gather data to artifact directory: %s\n", outputDir) |
| 143 | + } else { |
| 144 | + outputDir = outputDirName |
| 145 | + log.Printf("Collecting must-gather data to current directory: %s\n", outputDir) |
| 146 | + } |
| 147 | + |
| 148 | + // Try to get the must-gather image from the CSV |
| 149 | + mustGatherImage := "" |
| 150 | + |
| 151 | + // List all CSVs in the operator namespace |
| 152 | + csvList := &unstructured.UnstructuredList{} |
| 153 | + csvList.SetGroupVersionKind(metav1.GroupVersionKind{ |
| 154 | + Group: "operators.coreos.com", |
| 155 | + Version: "v1alpha1", |
| 156 | + Kind: "ClusterServiceVersionList", |
| 157 | + }) |
| 158 | + |
| 159 | + if err := f.Client.List(context.TODO(), csvList, client.InNamespace(f.OperatorNamespace)); err != nil { |
| 160 | + log.Printf("Warning: Failed to list CSVs: %v\n", err) |
| 161 | + } else { |
| 162 | + // Find the compliance-operator CSV |
| 163 | + for _, csv := range csvList.Items { |
| 164 | + if strings.Contains(csv.GetName(), "compliance-operator") { |
| 165 | + // Extract must-gather image from relatedImages |
| 166 | + relatedImages, found, err := unstructured.NestedSlice(csv.Object, "spec", "relatedImages") |
| 167 | + if err == nil && found { |
| 168 | + for _, img := range relatedImages { |
| 169 | + imgMap, ok := img.(map[string]interface{}) |
| 170 | + if !ok { |
| 171 | + continue |
| 172 | + } |
| 173 | + name, _, _ := unstructured.NestedString(imgMap, "name") |
| 174 | + if name == "must-gather" { |
| 175 | + image, _, _ := unstructured.NestedString(imgMap, "image") |
| 176 | + mustGatherImage = image |
| 177 | + break |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + break |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + // If we couldn't find the image from CSV, use the default |
| 187 | + if mustGatherImage == "" { |
| 188 | + mustGatherImage = "ghcr.io/complianceascode/must-gather-ocp:latest" |
| 189 | + log.Printf("Using default must-gather image: %s\n", mustGatherImage) |
| 190 | + } else { |
| 191 | + log.Printf("Using must-gather image from CSV: %s\n", mustGatherImage) |
| 192 | + } |
| 193 | + |
| 194 | + // Run oc adm must-gather |
| 195 | + log.Printf("Running: oc adm must-gather --image=%s --dest-dir=%s\n", mustGatherImage, outputDir) |
| 196 | + |
| 197 | + cmd := exec.Command("oc", "adm", "must-gather", fmt.Sprintf("--image=%s", mustGatherImage), fmt.Sprintf("--dest-dir=%s", outputDir)) |
| 198 | + cmd.Stdout = os.Stdout |
| 199 | + cmd.Stderr = os.Stderr |
| 200 | + |
| 201 | + if err := cmd.Run(); err != nil { |
| 202 | + log.Printf("Warning: Failed to collect must-gather data: %v\n", err) |
| 203 | + log.Printf("Must-gather data was not collected, but test will continue\n") |
| 204 | + } else { |
| 205 | + log.Printf("Successfully collected must-gather data to %s\n", outputDir) |
| 206 | + log.Printf("You can find the must-gather archive in the current directory\n") |
| 207 | + } |
| 208 | +} |
| 209 | + |
126 | 210 | func (f *Framework) PrintROSADebugInfo(t *testing.T) { |
127 | 211 | // List cluster claims |
128 | 212 | clusterClaimList := clusterv1alpha1.ClusterClaimList{} |
@@ -591,83 +675,19 @@ func (f *Framework) WaitForProfileBundleStatus(name string, status compv1alpha1. |
591 | 675 | return false, nil |
592 | 676 | }) |
593 | 677 | if timeouterr != nil { |
594 | | - // Log detailed debug information when ProfileBundle fails to reach the expected status |
595 | | - log.Printf("DEBUG: ProfileBundle %s failed to reach state %s\n", name, status) |
596 | | - log.Printf("DEBUG: Current status: %s\n", pb.Status.DataStreamStatus) |
597 | | - log.Printf("DEBUG: ContentImage: %s\n", pb.Spec.ContentImage) |
598 | | - log.Printf("DEBUG: ContentFile: %s\n", pb.Spec.ContentFile) |
| 678 | + // Log basic information about the failure |
| 679 | + log.Printf("ProfileBundle %s failed to reach state %s\n", name, status) |
| 680 | + log.Printf("Current status: %s\n", pb.Status.DataStreamStatus) |
| 681 | + log.Printf("ContentImage: %s\n", pb.Spec.ContentImage) |
| 682 | + log.Printf("ContentFile: %s\n", pb.Spec.ContentFile) |
599 | 683 |
|
600 | 684 | if pb.Status.ErrorMessage != "" { |
601 | | - log.Printf("DEBUG: ErrorMessage: %s\n", pb.Status.ErrorMessage) |
602 | | - } |
603 | | - |
604 | | - if len(pb.Status.Conditions) > 0 { |
605 | | - log.Printf("DEBUG: Conditions:\n") |
606 | | - for _, condition := range pb.Status.Conditions { |
607 | | - log.Printf(" - Type: %s, Status: %s, Reason: %s, Message: %s\n", |
608 | | - condition.Type, condition.Status, condition.Reason, condition.Message) |
609 | | - } |
610 | | - } else { |
611 | | - log.Printf("DEBUG: No conditions found\n") |
| 685 | + log.Printf("ErrorMessage: %s\n", pb.Status.ErrorMessage) |
612 | 686 | } |
613 | 687 |
|
614 | | - // Get and display parser pod information |
615 | | - log.Printf("DEBUG: Fetching parser pod information...\n") |
616 | | - pods, err := f.KubeClient.CoreV1().Pods(f.OperatorNamespace).List(context.TODO(), metav1.ListOptions{ |
617 | | - LabelSelector: fmt.Sprintf("profile-bundle=%s", name), |
618 | | - }) |
619 | | - if err != nil { |
620 | | - log.Printf("DEBUG: Failed to list parser pods: %v\n", err) |
621 | | - } else if len(pods.Items) == 0 { |
622 | | - log.Printf("DEBUG: No parser pods found for profile-bundle=%s\n", name) |
623 | | - } else { |
624 | | - log.Printf("DEBUG: Found %d parser pod(s):\n", len(pods.Items)) |
625 | | - for _, pod := range pods.Items { |
626 | | - log.Printf(" - Pod: %s, Phase: %s, Reason: %s, Message: %s\n", |
627 | | - pod.Name, pod.Status.Phase, pod.Status.Reason, pod.Status.Message) |
628 | | - |
629 | | - // Show container statuses |
630 | | - for _, containerStatus := range pod.Status.ContainerStatuses { |
631 | | - log.Printf(" Container: %s, Ready: %v, RestartCount: %d\n", |
632 | | - containerStatus.Name, containerStatus.Ready, containerStatus.RestartCount) |
633 | | - if containerStatus.State.Waiting != nil { |
634 | | - log.Printf(" Waiting: %s - %s\n", |
635 | | - containerStatus.State.Waiting.Reason, containerStatus.State.Waiting.Message) |
636 | | - } |
637 | | - if containerStatus.State.Terminated != nil { |
638 | | - log.Printf(" Terminated: %s (exit code %d) - %s\n", |
639 | | - containerStatus.State.Terminated.Reason, |
640 | | - containerStatus.State.Terminated.ExitCode, |
641 | | - containerStatus.State.Terminated.Message) |
642 | | - } |
643 | | - } |
644 | | - |
645 | | - // Get pod logs |
646 | | - log.Printf("DEBUG: Fetching logs for pod %s...\n", pod.Name) |
647 | | - logOpts := &corev1.PodLogOptions{ |
648 | | - TailLines: int64Ptr(50), |
649 | | - } |
650 | | - req := f.KubeClient.CoreV1().Pods(f.OperatorNamespace).GetLogs(pod.Name, logOpts) |
651 | | - podLogs, err := req.Stream(context.TODO()) |
652 | | - if err != nil { |
653 | | - log.Printf("DEBUG: Failed to get logs for pod %s: %v\n", pod.Name, err) |
654 | | - } else { |
655 | | - defer podLogs.Close() |
656 | | - buf := new(bytes.Buffer) |
657 | | - _, err = buf.ReadFrom(podLogs) |
658 | | - if err != nil { |
659 | | - log.Printf("DEBUG: Failed to read logs: %v\n", err) |
660 | | - } else { |
661 | | - logContent := buf.String() |
662 | | - if logContent != "" { |
663 | | - log.Printf("DEBUG: Last 50 lines of logs from %s:\n%s\n", pod.Name, logContent) |
664 | | - } else { |
665 | | - log.Printf("DEBUG: No logs available from pod %s\n", pod.Name) |
666 | | - } |
667 | | - } |
668 | | - } |
669 | | - } |
670 | | - } |
| 688 | + // Collect must-gather data for comprehensive debugging |
| 689 | + log.Printf("Collecting must-gather data for detailed debugging information...\n") |
| 690 | + f.CollectMustGather(fmt.Sprintf("ProfileBundle-%s", name)) |
671 | 691 |
|
672 | 692 | // Include error details in the returned error |
673 | 693 | errMsg := fmt.Sprintf("ProfileBundle %s failed to reach state %s (current: %s)", |
|
0 commit comments