Skip to content

HYPERFLEET-591, HYPERFLEET-593 - test: automate K8s Resources Check and Adapter Dependency Relationships Workflow Validation#27

Merged
openshift-merge-bot[bot] merged 3 commits intoopenshift-hyperfleet:mainfrom
86254860:HYPERFLEET-591
Feb 12, 2026
Merged

HYPERFLEET-591, HYPERFLEET-593 - test: automate K8s Resources Check and Adapter Dependency Relationships Workflow Validation#27
openshift-merge-bot[bot] merged 3 commits intoopenshift-hyperfleet:mainfrom
86254860:HYPERFLEET-591

Conversation

@86254860
Copy link
Contributor

@86254860 86254860 commented Feb 12, 2026

Summary

Testing

./bin/hyperfleet-e2e test --label-filter=tier0
Ran 3 of 4 Specs in 208.099 seconds
SUCCESS! -- 3 Passed | 0 Failed | 0 Pending | 1 Skipped

Summary by CodeRabbit

  • New Features

    • Added two cluster adapters: cl-job and cl-deployment.
  • Configuration

    • Increased adapter processing timeout from 1m to 2m.
  • Tests

    • Redesigned cluster e2e tests into phased suites with richer validation, per-adapter Kubernetes resource checks, templating/metadata verification, explicit adapter dependency scenarios, polling/timeouts, improved logging and cleanup.
  • Quality

    • Added Kubernetes resource verification and name/label validation utilities to strengthen checks.

…nd Adapter Dependency Relationships Workflow Validation
@openshift-ci openshift-ci bot requested review from aredenba-rh and yasun1 February 12, 2026 01:23
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR increases the adapter processing timeout in configs/config.yaml, adds two cluster adapters (cl-job, cl-deployment) to config, introduces Kubernetes verification helpers in pkg/helper/k8s.go (namespace/job/deployment checks and label-selector builder), adds validation utilities and an adapter-name→condition mapping in pkg/helper/validation.go, and restructures e2e cluster tests (e2e/cluster/creation.go and test-design/testcases/cluster.md) into phased checks with per-adapter Kubernetes resource verification and dependency validation where cl-deployment waits on cl-job. No exported/public API removals.

Sequence Diagram(s)

sequenceDiagram
    participant Test as E2E Test
    participant Suite as Test Suite (creation.go)
    participant Helper as Helper (pkg/helper)
    participant Kubectl as kubectl CLI
    participant K8sAPI as Kubernetes API

    Test->>Suite: trigger cluster creation
    Suite->>Helper: request per-adapter verification
    Helper->>Kubectl: kubectl get <resource> --selector=<labels> -o json
    Kubectl->>K8sAPI: fetch resource
    K8sAPI-->>Kubectl: JSON response
    Kubectl-->>Helper: JSON output
    Helper->>Helper: parse JSON, validate status/labels/annotations
    alt cl-deployment depends on cl-job
        Helper->>Kubectl: poll Job status (selector)
        Kubectl->>K8sAPI: fetch job status
        K8sAPI-->>Kubectl: job status (succeeded)
        Kubectl-->>Helper: job complete
        Helper->>Kubectl: verify Deployment available
        Kubectl-->>Helper: deployment available
    end
    Helper-->>Suite: verification result
    Suite-->>Test: test outcome
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

lgtm, approved

Suggested reviewers

  • aredenba-rh
  • xueli181114
  • tzhou5
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references the two ticket IDs (HYPERFLEET-591, HYPERFLEET-593) and accurately describes the main changes: automating K8s Resources Check and Adapter Dependency Relationships Workflow Validation tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test-design/testcases/cluster.md (1)

5-7: ⚠️ Potential issue | 🟡 Minor

Update the Table of Contents entry to match the renamed section.

The TOC still points to the old “Workflow Validation” anchor, which will break the link after the heading rename.

🛠️ Proposed fix
-1. [Clusters Resource Type - Workflow Validation](`#test-title-clusters-resource-type---workflow-validation`)
+1. [Clusters Resource Type - Basic Workflow Validation](`#test-title-clusters-resource-type---basic-workflow-validation`)

Also applies to: 10-10

🤖 Fix all issues with AI agents
In `@e2e/cluster/creation.go`:
- Around line 211-216: The loop over h.Cfg.Adapters.Cluster currently prints a
warning and continues when adapterResourceVerifiers lacks an entry, which hides
missing verifier errors; update the logic in the loop that inspects
adapterResourceVerifiers (using variables adapterName, verifier, exists) to fail
the test immediately instead of skipping: replace the ginkgo.GinkgoWriter.Printf
+ continue with a test-failing call (e.g., ginkgo.Fail or a Gomega Expect/Fail)
that includes the adapterName and clear context so the test run exits fast when
a verifier is missing.
- Around line 240-279: The boolean flag foundInitialState is declared outside
the Eventually poll closure and persists across retries; move its initialization
into the start of the polling closure so each poll sets foundInitialState =
false before calling h.Client.GetClusterStatuses and iterating adapters, then
keep the existing checks (HasAdapterCondition calls and
g.Expect(foundInitialState)) unchanged so the assertion reflects the adapter
presence/state for the current poll rather than stale state from prior retries.

In `@pkg/helper/k8s.go`:
- Around line 130-137: The current logic casts jobList["items"] to items and
unconditionally picks items[0], which is nondeterministic when multiple
resources match; update the code that handles items (the jobList/items/jobData
logic) to check for multiple matches and return a clear error if len(items) > 1
(or alternatively filter by a provided name/explicit identifier) instead of
silently using the first entry, and apply the same change to the other identical
items-handling block that currently casts and uses the first element.

var clusterName string

ginkgo.BeforeEach(func() {
ginkgo.BeforeEach(func(ctx context.Context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to delete the resources created by adapters? Like namespace, job ... Add it with cluster deletion action after MVP?
I think we can add a common function for every added adapter. Then it can call it directly after test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For MVP and E2E testing, we delete the namespace named clusterid in AfterEach. All created Kubernetes resources are scoped under this namespace, so deleting it removes all cluster-related resources.

For post-MVP, the created Kubernetes resources should be cleaned up when a DELETE cluster request is submitted. If the deletion fails, it indicates an issue that CLM needs to resolve.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you add the AfterEach? I didn't find it

Copy link
Contributor Author

@86254860 86254860 Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three test cases use the same BeforeEach and AfterEach

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
configs/config.yaml (1)

48-55: ⚠️ Potential issue | 🟡 Minor

Update the recommendation comment to match the new default.
The comment still says “Recommended: 1m” while the default is now 2m, which can mislead readers.

📝 Suggested doc alignment
-    # Recommended: 1m
+    # Recommended: 2m
🧹 Nitpick comments (1)
pkg/helper/validation.go (1)

59-79: Normalize adapter names before conversion.
Trimming whitespace (and short-circuiting empty values) avoids surprising condition types when config entries include accidental spaces.

♻️ Proposed tweak
 func (h *Helper) AdapterNameToClusterConditionType(adapterName string) string {
+    adapterName = strings.TrimSpace(adapterName)
+    if adapterName == "" {
+        return ""
+    }
     // Split adapter name by "-" (e.g., "cl-namespace" -> ["cl", "namespace"])
     parts := strings.Split(adapterName, "-")

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pkg/helper/validation.go`:
- Around line 11-30: ValidateK8sName currently only checks total length and
k8sNameRegex but doesn't enforce the DNS-1123 per-label (dot-separated)
63-character limit; update ValidateK8sName to split the name on '.' (use
strings.Split) and iterate labels to ensure each label is non-empty and
len(label) <= 63, returning an error like "label %q exceeds maximum length of 63
characters" if violated; keep the existing regex and overall 253-char check, and
reference k8sNameRegex and ValidateK8sName when making this change.
- Around line 32-41: The ValidateLabelSelector function currently uses
labelSelectorRegex and rejects many valid Kubernetes selectors; replace the
regex check with the Kubernetes parser by calling labels.Parse(selector) from
k8s.io/apimachinery/pkg/labels inside ValidateLabelSelector and return any parse
error (and remove labelSelectorRegex since it becomes unused). Keep the
empty-string guard, but instead of labelSelectorRegex.MatchString(selector) use
labels.Parse(selector) to validate formats like set-based, existence, != and
DNS-prefixed keys.
🧹 Nitpick comments (1)
pkg/helper/validation.go (1)

93-114: Guard against empty adapter names.
An empty or all-- adapter name currently returns "Successful", which can silently mis-map conditions. Consider a defensive early return.

🛡️ Defensive guard
 func (h *Helper) AdapterNameToClusterConditionType(adapterName string) string {
+    if strings.Trim(adapterName, "-") == "" {
+        return ""
+    }
     // Split adapter name by "-" (e.g., "cl-namespace" -> ["cl", "namespace"])
     parts := strings.Split(adapterName, "-")

Comment on lines +11 to +30
var (
// k8sNameRegex validates Kubernetes resource names (DNS-1123 subdomain format)
k8sNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
// labelSelectorRegex validates Kubernetes label selector format (key=value,key=value)
labelSelectorRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?(,[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?)*$`)
)

// ValidateK8sName validates a Kubernetes resource name (namespace, resource name, etc.)
func ValidateK8sName(name string) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
if len(name) > 253 {
return fmt.Errorf("name %q exceeds maximum length of 253 characters", name)
}
if !k8sNameRegex.MatchString(name) {
return fmt.Errorf("name %q is invalid: must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", name)
}
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "validation.go" -path "*/helper/*"

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 105


🏁 Script executed:

cat -n pkg/helper/validation.go | head -40

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 1972


🌐 Web query:

DNS-1123 subdomain format Kubernetes resource naming label length limit

💡 Result:

  • DNS-1123 label (often called “DNS label” in Kubernetes)

    • Max length: 63 characters
    • Format: lowercase alphanumeric (a-z0-9) or -; must start with a letter; must end with an alphanumeric. [1]
  • DNS-1123 subdomain (used by most metadata.name fields)

    • Max length: 253 characters total
    • Format: one or more DNS labels separated by .; allowed chars are lowercase alphanumeric, -, and .; must start and end with an alphanumeric. [1]
    • Implied per-label limit: each label inside the subdomain is still limited to 63 characters (DNS rule). [3]

Sources: Kubernetes naming rules [1]; RFC 1123 label and total-length limits [3].


Enforce per-label 63-character limit for DNS-1123 compliance.

The current regex and validation accept names with labels longer than 63 characters, which violates DNS-1123 subdomain format. Each dot-separated label must not exceed 63 characters in addition to the total 253-character limit. This allows invalid resource names that the Kubernetes API will reject.

Add a per-label length check:

Fix to enforce per-label length limit
 func ValidateK8sName(name string) error {
     if name == "" {
         return fmt.Errorf("name cannot be empty")
     }
     if len(name) > 253 {
         return fmt.Errorf("name %q exceeds maximum length of 253 characters", name)
     }
+    for _, label := range strings.Split(name, ".") {
+        if len(label) > 63 {
+            return fmt.Errorf("name %q has a label longer than 63 characters", name)
+        }
+    }
     if !k8sNameRegex.MatchString(name) {
         return fmt.Errorf("name %q is invalid: must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", name)
     }
     return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var (
// k8sNameRegex validates Kubernetes resource names (DNS-1123 subdomain format)
k8sNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
// labelSelectorRegex validates Kubernetes label selector format (key=value,key=value)
labelSelectorRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?(,[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?)*$`)
)
// ValidateK8sName validates a Kubernetes resource name (namespace, resource name, etc.)
func ValidateK8sName(name string) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
if len(name) > 253 {
return fmt.Errorf("name %q exceeds maximum length of 253 characters", name)
}
if !k8sNameRegex.MatchString(name) {
return fmt.Errorf("name %q is invalid: must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", name)
}
return nil
}
var (
// k8sNameRegex validates Kubernetes resource names (DNS-1123 subdomain format)
k8sNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
// labelSelectorRegex validates Kubernetes label selector format (key=value,key=value)
labelSelectorRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?(,[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?=[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?)*$`)
)
// ValidateK8sName validates a Kubernetes resource name (namespace, resource name, etc.)
func ValidateK8sName(name string) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
if len(name) > 253 {
return fmt.Errorf("name %q exceeds maximum length of 253 characters", name)
}
for _, label := range strings.Split(name, ".") {
if len(label) > 63 {
return fmt.Errorf("name %q has a label longer than 63 characters", name)
}
}
if !k8sNameRegex.MatchString(name) {
return fmt.Errorf("name %q is invalid: must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", name)
}
return nil
}
🤖 Prompt for AI Agents
In `@pkg/helper/validation.go` around lines 11 - 30, ValidateK8sName currently
only checks total length and k8sNameRegex but doesn't enforce the DNS-1123
per-label (dot-separated) 63-character limit; update ValidateK8sName to split
the name on '.' (use strings.Split) and iterate labels to ensure each label is
non-empty and len(label) <= 63, returning an error like "label %q exceeds
maximum length of 63 characters" if violated; keep the existing regex and
overall 253-char check, and reference k8sNameRegex and ValidateK8sName when
making this change.

Comment on lines +32 to +41
// ValidateLabelSelector validates a Kubernetes label selector string
func ValidateLabelSelector(selector string) error {
if selector == "" {
return fmt.Errorf("label selector cannot be empty")
}
if !labelSelectorRegex.MatchString(selector) {
return fmt.Errorf("label selector %q is invalid: must be in format 'key=value' or 'key1=value1,key2=value2'", selector)
}
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n pkg/helper/validation.go | head -60

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 2940


🏁 Script executed:

cat go.mod | grep -i "k8s.io"

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 61


🏁 Script executed:

rg "ValidateLabelSelector" --type go

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 416


🏁 Script executed:

ls -la go.mod

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 134


🏁 Script executed:

cat -n pkg/helper/k8s.go | grep -A 5 -B 5 "ValidateLabelSelector"

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 1037


🏁 Script executed:

cat go.mod

Repository: openshift-hyperfleet/hyperfleet-e2e

Length of output: 1859


🌐 Web query:

Kubernetes label selector validation formats equality set-based existence

💡 Result:

Kubernetes label selector formats (and what gets validated)

1) Equality-based (string selectors: kubectl -l ..., ?labelSelector=...)

Operators: =, == (synonyms), !=
Examples: environment=production, tier!=frontend
Note: key!=value also matches objects without that key. Multiple requirements are comma-separated and are ANDed. No OR operator. [1]

2) Set-based (string selectors)

Operators / forms:

  • In: key in (v1,v2)
  • NotIn: key notin (v1,v2) (also matches objects without that key)
  • Exists: key
  • DoesNotExist: !key
    Multiple requirements are comma-separated and ANDed. [1]

3) Existence (explicitly)

  • Exists: partition
  • DoesNotExist: !partition [1]

Validation rules you typically hit

A) Label key / value format validation (applies to labels used in selectors too)

  • Key: optional prefix/ + required name
    • name: ≤ 63 chars; begins/ends with alphanumeric; may contain -, _, ., alphanumerics in between
    • prefix (if present): DNS subdomain (≤ 253 chars) followed by / [1]
  • Value: ≤ 63 chars (can be empty); if non-empty must begin/end with alphanumeric; may contain -, _, ., alphanumerics in between [1]

B) API LabelSelector / matchExpressions validation (YAML/JSON selectors)

In structured selectors (spec.selector.matchExpressions):

  • operator{In, NotIn, Exists, DoesNotExist}
  • values rules:
    • In / NotInvalues must be non-empty
    • Exists / DoesNotExistvalues must be empty [2]

Sources: Kubernetes “Labels and Selectors” docs [1]; Kubernetes API “LabelSelector” definition (matchExpressions rules) [2].

Citations: [1] (kubernetes.io) [2] (kubernetes.io)

Citations:


Replace regex validation with Kubernetes' official label selector parser.
The current regex only supports equality-based selectors (key=value), rejecting valid formats including set-based selectors (key in (v1,v2), key notin (v1,v2)), existence selectors (key, !key), inequality operators (!=), and DNS-prefixed keys (prefix/key). Use labels.Parse() from k8s.io/apimachinery/pkg/labels for correct validation.

✅ Use Kubernetes' parser for validation
+import (
+    ...
+    "k8s.io/apimachinery/pkg/labels"
+)
+
 func ValidateLabelSelector(selector string) error {
     if selector == "" {
         return fmt.Errorf("label selector cannot be empty")
     }
-    if !labelSelectorRegex.MatchString(selector) {
-        return fmt.Errorf("label selector %q is invalid: must be in format 'key=value' or 'key1=value1,key2=value2'", selector)
-    }
+    if _, err := labels.Parse(selector); err != nil {
+        return fmt.Errorf("label selector %q is invalid: %w", selector, err)
+    }
     return nil
 }

If you switch to labels.Parse, labelSelectorRegex becomes unnecessary and can be removed.

🤖 Prompt for AI Agents
In `@pkg/helper/validation.go` around lines 32 - 41, The ValidateLabelSelector
function currently uses labelSelectorRegex and rejects many valid Kubernetes
selectors; replace the regex check with the Kubernetes parser by calling
labels.Parse(selector) from k8s.io/apimachinery/pkg/labels inside
ValidateLabelSelector and return any parse error (and remove labelSelectorRegex
since it becomes unused). Keep the empty-string guard, but instead of
labelSelectorRegex.MatchString(selector) use labels.Parse(selector) to validate
formats like set-based, existence, != and DNS-prefixed keys.

@yasun1
Copy link
Contributor

yasun1 commented Feb 12, 2026

/lgtm

@openshift-ci
Copy link

openshift-ci bot commented Feb 12, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: yasun1

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot bot merged commit c41f56a into openshift-hyperfleet:main Feb 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants