Skip to content

Commit 3afe11d

Browse files
stubbiclaude
andcommitted
fix(conformance): wait for operator Deployment to be Available before specs
The idempotency conformance job ran `make deploy` and immediately executed the suite. `make deploy` only applies manifests (kustomize build | kubectl apply --server-side) and returns before the controller-manager Pod has pulled its image, started, and won leader election. On a cold kind cluster the first Instance was therefore applied while nothing was reconciling, and the 2m owned-resource wait expired ("did not have its owned StatefulSet and Service created within 2m0s"). openclaw-operator's conformance harness does not hit this because it installs the operator via `helm upgrade --install ... --wait --timeout=10m`, which blocks until the Deployment is Available. Paperclip's harness had no equivalent gate. Fix (mirrors openclaw): - conformance.yaml: after `make deploy`, block on `kubectl rollout status` and `kubectl wait --for=condition=Available` for the controller-manager Deployment before running the suite. - BeforeSuite: wait for the operator Deployment to be Available before any Instance is created (defense-in-depth for local runs). - Bump idempotencyCreateWait 2m -> 5m for headroom on a busy kind cluster. No conformance assertions are weakened; the gate stays meaningful. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0b0ea44 commit 3afe11d

3 files changed

Lines changed: 29 additions & 2 deletions

File tree

.github/workflows/conformance.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ jobs:
5252
kind load docker-image paperclip-operator:dev --name paperclip-conformance
5353
- name: Deploy operator
5454
run: make deploy IMG=paperclip-operator:dev
55+
- name: Wait for operator to be Available
56+
# make deploy only applies manifests and returns immediately; the
57+
# controller-manager Pod still has to pull its image, start, and win
58+
# leader election before it reconciles anything. Block here until the
59+
# Deployment is Available so the conformance suite never races the
60+
# operator. Mirrors openclaw-operator's `helm install --wait`.
61+
run: |
62+
kubectl rollout status \
63+
deploy -l control-plane=controller-manager \
64+
-n paperclip-operator-system --timeout=10m
65+
kubectl wait --for=condition=Available \
66+
deploy -l control-plane=controller-manager \
67+
-n paperclip-operator-system --timeout=5m
5568
- name: Run idempotency conformance
5669
env:
5770
KUBECONFIG: /home/runner/.kube/config

test/conformance/conformance_suite_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ var _ = BeforeSuite(func() {
5454
if os.Getenv("KUBECONFIG") == "" {
5555
Skip("KUBECONFIG not set: conformance suite requires a live kind cluster with the operator installed")
5656
}
57+
// `make deploy` only applies manifests and returns immediately. Wait for
58+
// the controller-manager Deployment to be Available before any spec creates
59+
// an Instance, otherwise the first specs race the operator's image pull,
60+
// startup, and leader election and time out waiting for owned resources.
61+
// CI also waits via `kubectl rollout status`; this is defense-in-depth for
62+
// local runs and mirrors openclaw-operator's helm `--wait` install.
63+
// (operatorNamespace() lives in failure_modes_test.go, same package.)
64+
opNS := operatorNamespace()
65+
out, err := kubectl("wait", "--for=condition=Available",
66+
"deployment", "-n", opNS, "-l", "control-plane=controller-manager", "--timeout=5m")
67+
Expect(err).ToNot(HaveOccurred(),
68+
"operator Deployment in %s never became Available: %s", opNS, out)
5769
})
5870

5971
var _ = AfterSuite(func() {

test/conformance/idempotency_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ const (
5353
idempotencyReconciles = 10
5454
// idempotencyCreateWait bounds how long we wait for the operator to create
5555
// the instance's owned StatefulSet and Service. This is creation only (no
56-
// image pull or app boot), so it settles quickly even on kind.
57-
idempotencyCreateWait = 2 * time.Minute
56+
// image pull or app boot). The suite already waits for the operator to be
57+
// Available before this point, so creation settles quickly; the generous
58+
// bound is headroom for a busy kind cluster.
59+
idempotencyCreateWait = 5 * time.Minute
5860
idempotencyPokeWait = 15 * time.Second
5961
)
6062

0 commit comments

Comments
 (0)