@@ -27,8 +27,10 @@ import (
2727 "strings"
2828 "time"
2929
30+ apierrors "k8s.io/apimachinery/pkg/api/errors"
3031 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3132 "k8s.io/apimachinery/pkg/runtime/schema"
33+ "k8s.io/apimachinery/pkg/util/wait"
3234 "k8s.io/client-go/dynamic"
3335 "k8s.io/kops/tests/e2e/scenarios/ai-conformance/testartifacts"
3436
@@ -193,12 +195,29 @@ func (h *ValidatorHarness) TestNamespace() string {
193195
194196 h .t .Cleanup (func () {
195197 ctx := context .WithoutCancel (h .Context ())
196- h .dumpNamespaceResources (ctx , ns )
198+ h .dumpNamespaceResources (ctx , ns , "cluster-info" )
199+
200+ startTime := time .Now ()
197201
198202 h .Logf ("Deleting test namespace %q" , ns )
199- err := h .DynamicClient ().Resource (namespaceGVR ).Delete (ctx , ns , metav1.DeleteOptions {})
200- if err != nil {
201- h .Logf ("failed to delete test namespace: %v" , err )
203+ if err := h .DynamicClient ().Resource (namespaceGVR ).Delete (ctx , ns , metav1.DeleteOptions {}); err != nil {
204+ h .Errorf ("failed to delete test namespace: %v" , err )
205+ }
206+
207+ // Wait for namespace deletion to complete so that we don't have leftover namespaces consuming resources.
208+ if err := wait .PollUntilContextTimeout (ctx , 2 * time .Second , 5 * time .Minute , false , func (ctx context.Context ) (done bool , err error ) {
209+ if _ , err := h .DynamicClient ().Resource (namespaceGVR ).Get (ctx , ns , metav1.GetOptions {}); err != nil {
210+ if apierrors .IsNotFound (err ) {
211+ return true , nil
212+ }
213+ return false , fmt .Errorf ("error checking for namespace deletion: %w" , err )
214+ }
215+ return false , nil
216+ }); err != nil {
217+ h .Errorf ("error waiting for namespace deletion: %v" , err )
218+ h .dumpNamespaceResources (ctx , ns , "namespace-deletion-failure-info" )
219+ } else {
220+ h .Logf ("Namespace deletion took %s" , time .Since (startTime ).Round (time .Second ))
202221 }
203222 })
204223 }
@@ -225,8 +244,8 @@ func (h *ValidatorHarness) ApplyManifest(defaultNamespace string, manifestPath s
225244}
226245
227246// dumpNamespaceResources dumps key resources from the namespace to the artifacts directory for debugging.
228- func (h * ValidatorHarness ) dumpNamespaceResources (ctx context.Context , ns string ) {
229- clusterInfoDir := testartifacts .PathForTestArtifact (h .t , "cluster-info" )
247+ func (h * ValidatorHarness ) dumpNamespaceResources (ctx context.Context , ns string , outputDir string ) {
248+ clusterInfoDir := testartifacts .PathForTestArtifact (h .t , outputDir )
230249 clusterInfoDir = filepath .Join (clusterInfoDir , ns )
231250
232251 if err := os .MkdirAll (clusterInfoDir , 0o755 ); err != nil {
@@ -244,6 +263,7 @@ func (h *ValidatorHarness) dumpNamespaceResources(ctx context.Context, ns string
244263 // Always include Events, Pods: they are usually not in the manifest, but are often critical for understanding failures.
245264 resourceTypes ["Events" ] = true
246265 resourceTypes ["Pods" ] = true
266+ resourceTypes ["Nodes" ] = true
247267
248268 for resourceType := range resourceTypes {
249269 filename := strings .ToLower (resourceType ) + ".yaml"
@@ -252,13 +272,20 @@ func (h *ValidatorHarness) dumpNamespaceResources(ctx context.Context, ns string
252272 }
253273 }
254274
275+ describeResourcesTypes := []string {"nodes" , "pods" }
276+ for _ , describeResourcesType := range describeResourcesTypes {
277+ filename := strings .ToLower (describeResourcesType ) + ".txt"
278+ if err := h .kubectlDescribeResource (ctx , ns , describeResourcesType , filepath .Join (clusterInfoDir , filename )); err != nil {
279+ h .Logf ("failed to kubectl describe resource %s: %v" , describeResourcesType , err )
280+ }
281+ }
282+
255283 if err := h .dumpPodLogs (ctx , ns , clusterInfoDir ); err != nil {
256284 h .Logf ("failed to dump pod logs: %v" , err )
257285 }
258286}
259287
260288// dumpResource runs kubectl get for a resource type and writes the output to a file.
261- // Errors are logged but do not fail the test.
262289func (h * ValidatorHarness ) dumpResource (ctx context.Context , ns string , resourceType string , outputPath string ) error {
263290 args := []string {"get" , resourceType }
264291 if ns != "" {
@@ -282,6 +309,29 @@ func (h *ValidatorHarness) dumpResource(ctx context.Context, ns string, resource
282309 return nil
283310}
284311
312+ // dumpResource runs kubectl describe for a resource type and writes the output to a file.
313+ func (h * ValidatorHarness ) kubectlDescribeResource (ctx context.Context , ns string , resourceType string , outputPath string ) error {
314+ args := []string {"describe" , resourceType }
315+ if ns != "" {
316+ args = append (args , "-n" , ns )
317+ }
318+ cmd := exec .CommandContext (ctx , "kubectl" , args ... )
319+ var stdout bytes.Buffer
320+ var stderr bytes.Buffer
321+ cmd .Stdout = & stdout
322+ cmd .Stderr = & stderr
323+
324+ if err := cmd .Run (); err != nil {
325+ return fmt .Errorf ("failed to kubectl describe %s in namespace %s: %v (stderr: %s)" , resourceType , ns , err , stderr .String ())
326+ }
327+
328+ if err := os .WriteFile (outputPath , stdout .Bytes (), 0o644 ); err != nil {
329+ return fmt .Errorf ("failed to write %s describe output to %s: %w" , resourceType , outputPath , err )
330+ }
331+
332+ return nil
333+ }
334+
285335// dumpPodLogs collects logs from all pods in the namespace and writes them to individual files.
286336func (h * ValidatorHarness ) dumpPodLogs (ctx context.Context , ns string , clusterInfoDir string ) error {
287337 podLogsDir := filepath .Join (clusterInfoDir , "pod-logs" )
0 commit comments