@@ -14,6 +14,7 @@ import (
1414
1515 configv1 "github.com/openshift/api/config/v1"
1616 "github.com/pkg/errors"
17+ "golang.org/x/mod/semver"
1718 appsv1 "k8s.io/api/apps/v1"
1819 corev1 "k8s.io/api/core/v1"
1920 v1 "k8s.io/api/core/v1"
@@ -25,6 +26,7 @@ import (
2526 "k8s.io/client-go/rest"
2627 "k8s.io/client-go/tools/portforward"
2728 "k8s.io/client-go/transport/spdy"
29+ "k8s.io/utils/ptr"
2830 "sigs.k8s.io/controller-runtime/pkg/client"
2931)
3032
@@ -257,3 +259,96 @@ func (f *Framework) CleanUp(t *testing.T, cleanupFunc func()) {
257259 }
258260 })
259261}
262+
263+ // SkipIfClusterVersionBelow skips the test if the cluster version is below
264+ // minVersion. The minVersion string should be a semver-compatible version
265+ // (e.g. "4.19" or "v4.19").
266+ func (f * Framework ) SkipIfClusterVersionBelow (t * testing.T , minVersion string ) {
267+ t .Helper ()
268+ cv := & configv1.ClusterVersion {}
269+ err := f .K8sClient .Get (t .Context (), client.ObjectKey {Name : "version" }, cv )
270+ if err != nil {
271+ t .Skipf ("Skipping: unable to determine cluster version: %v" , err )
272+ return
273+ }
274+
275+ actual := cv .Status .Desired .Version
276+ if actual == "" {
277+ t .Skip ("Skipping: cluster version is empty" )
278+ return
279+ }
280+ t .Logf ("Detected cluster version: %s" , actual )
281+
282+ if ! strings .HasPrefix (actual , "v" ) {
283+ actual = "v" + actual
284+ }
285+ if ! strings .HasPrefix (minVersion , "v" ) {
286+ minVersion = "v" + minVersion
287+ }
288+
289+ canonicalActual := fmt .Sprintf ("%s-0" , semver .Canonical (actual ))
290+ canonicalMin := fmt .Sprintf ("%s-0" , semver .Canonical (minVersion ))
291+
292+ if semver .Compare (canonicalActual , canonicalMin ) < 0 {
293+ t .Skipf ("Skipping: cluster version %s is below minimum required %s" , cv .Status .Desired .Version , minVersion )
294+ }
295+ }
296+
297+ // DumpNamespaceDebug logs deployments (with conditions), pods (with container
298+ // statuses), and events for the given namespace. Useful as a t.Cleanup or
299+ // on-failure diagnostic helper.
300+ func (f * Framework ) DumpNamespaceDebug (t * testing.T , namespace string ) {
301+ t .Helper ()
302+ ctx := t .Context ()
303+
304+ t .Log ("=== BEGIN DEBUG DUMP ===" )
305+ defer t .Log ("=== END DEBUG DUMP ===" )
306+
307+ var deployments appsv1.DeploymentList
308+ if err := f .K8sClient .List (ctx , & deployments , client .InNamespace (namespace )); err != nil {
309+ t .Logf ("Failed to list deployments in %s: %v" , namespace , err )
310+ } else {
311+ t .Logf ("Deployments in namespace %s: %d" , namespace , len (deployments .Items ))
312+ for _ , d := range deployments .Items {
313+ t .Logf (" Deployment: name=%s replicas=%d readyReplicas=%d availableReplicas=%d" ,
314+ d .Name , ptr .Deref (d .Spec .Replicas , 0 ), d .Status .ReadyReplicas , d .Status .AvailableReplicas )
315+ for _ , c := range d .Status .Conditions {
316+ t .Logf (" condition: type=%s status=%s reason=%s message=%s" ,
317+ c .Type , c .Status , c .Reason , c .Message )
318+ }
319+ }
320+ }
321+
322+ var pods corev1.PodList
323+ if err := f .K8sClient .List (ctx , & pods , client .InNamespace (namespace )); err != nil {
324+ t .Logf ("Failed to list pods in %s: %v" , namespace , err )
325+ } else {
326+ t .Logf ("Pods in namespace %s: %d" , namespace , len (pods .Items ))
327+ for _ , p := range pods .Items {
328+ t .Logf (" Pod: name=%s phase=%s" , p .Name , p .Status .Phase )
329+ for _ , cs := range p .Status .ContainerStatuses {
330+ switch {
331+ case cs .State .Running != nil :
332+ t .Logf (" container=%s ready=%v restarts=%d state=Running" , cs .Name , cs .Ready , cs .RestartCount )
333+ case cs .State .Waiting != nil :
334+ t .Logf (" container=%s ready=%v restarts=%d state=Waiting reason=%s message=%s" ,
335+ cs .Name , cs .Ready , cs .RestartCount , cs .State .Waiting .Reason , cs .State .Waiting .Message )
336+ case cs .State .Terminated != nil :
337+ t .Logf (" container=%s ready=%v restarts=%d state=Terminated reason=%s exitCode=%d" ,
338+ cs .Name , cs .Ready , cs .RestartCount , cs .State .Terminated .Reason , cs .State .Terminated .ExitCode )
339+ }
340+ }
341+ }
342+ }
343+
344+ var events corev1.EventList
345+ if err := f .K8sClient .List (ctx , & events , client .InNamespace (namespace )); err != nil {
346+ t .Logf ("Failed to list events in %s: %v" , namespace , err )
347+ } else {
348+ t .Logf ("Events in namespace %s: %d" , namespace , len (events .Items ))
349+ for _ , e := range events .Items {
350+ t .Logf (" Event: involvedObject=%s/%s reason=%s message=%s type=%s count=%d" ,
351+ e .InvolvedObject .Kind , e .InvolvedObject .Name , e .Reason , e .Message , e .Type , e .Count )
352+ }
353+ }
354+ }
0 commit comments