From 1d7d383d309ccaa5c202dc3e0a187c424332e2ce Mon Sep 17 00:00:00 2001 From: HarshwardhanPatil07 Date: Mon, 2 Nov 2026 17:40:30 +0530 Subject: [PATCH 1/2] test(e2e): increase fallback polling interval for test deployments Worker provisioning and staging can exceed the five-minute polling bound in TestExternalBootcStatusChange. Configure a ten-minute interval in the bink E2E deployment and preserve it through operator upgrades, while keeping the production default at five minutes. Expose optional daemon arguments in the Helm chart and use the same E2E setting for deployment and the test assertion. The test remains parallel and verifies the running daemon received the configured interval. Fixes: #284 Assisted-by: AI Signed-off-by: HarshwardhanPatil07 --- Makefile | 7 ++++++- chart/bootc-operator/values.yaml | 3 +++ docs/design/IMPLEMENTATION_PLAN.md | 7 ++++--- hack/sync-helm.sh | 7 +++++++ test/e2e/upgrade_test.go | 5 ++++- test/e2e/watcher_test.go | 16 ++++++++++------ 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 8ea3cf02..e842bec1 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,9 @@ go-lines-fix: get-golines vulncheck: govulncheck ## Check Go code for known vulnerabilities (requires network access to the Go vulnerability database). "$(GOVULNCHECK)" ./... +# Give external-status observation room for provisioning without changing the daemon default. +E2E_BOOTC_POLL_INTERVAL ?= 10m + .PHONY: e2e e2e: ## Run e2e tests (requires: make deploy-bink). V=1 for verbose. RUN= to filter. # NB: we `cd` here instead of passing a package path to `go test` so that `-v` @@ -113,6 +116,7 @@ e2e: ## Run e2e tests (requires: make deploy-bink). V=1 for verbose. RUN= rm -rf $(ARTIFACTS) cd test/e2e && KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) \ E2E_PROVIDER=bink \ + E2E_BOOTC_POLL_INTERVAL=$(E2E_BOOTC_POLL_INTERVAL) \ BINK_CLUSTER_NAME=$(BINK_CLUSTER_NAME) \ BINK_NODE_DISK_IMAGE=$(BINK_NODE_DISK_IMAGE) \ E2E_NODE_IMAGE_REGISTRY=$(BINK_LOCAL_REGISTRY_NODE_IMAGE) \ @@ -270,7 +274,8 @@ deploy-bink: start-bink build-update-image $(if $(RELEASED_OPERATOR_TAG),push-re --set-string image.repository="$${img%:*}" \ --set-string image.tag="$${img##*:}" \ --set-string image.pullPolicy=Always \ - --set-json 'controller.extraArgs=["--allow-insecure-registry","--tag-resolution-interval=10s"]' + --set-json 'controller.extraArgs=["--allow-insecure-registry","--tag-resolution-interval=10s"]' \ + --set-string 'daemon.extraArgs[0]=--bootc-poll-interval=$(E2E_BOOTC_POLL_INTERVAL)' kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout restart deployment/bootc-operator-controller kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout status deployment/bootc-operator-controller --timeout=3m diff --git a/chart/bootc-operator/values.yaml b/chart/bootc-operator/values.yaml index 383c3705..110f186b 100644 --- a/chart/bootc-operator/values.yaml +++ b/chart/bootc-operator/values.yaml @@ -12,3 +12,6 @@ imagePullSecrets: [] controller: extraArgs: [] + +daemon: + extraArgs: [] diff --git a/docs/design/IMPLEMENTATION_PLAN.md b/docs/design/IMPLEMENTATION_PLAN.md index 6a1fd265..f2eb42b7 100644 --- a/docs/design/IMPLEMENTATION_PLAN.md +++ b/docs/design/IMPLEMENTATION_PLAN.md @@ -251,9 +251,10 @@ new image and the daemon reports `Idle=True`. **Validation (e2e):** ✅ On bink, `TestExternalBootcStatusChange` runs `bootc switch` on the host and verifies the daemon publishes the staged image within 30 seconds of the command completing. It requires a new -status update before the first possible fallback poll, using the same -default interval as the daemon. It verifies that the BootcNode generation -stayed unchanged and the daemon pod was neither replaced nor restarted. +status update before the first possible fallback poll, using the configured +E2E interval (10 minutes by default). It verifies that the BootcNode +generation stayed unchanged and the daemon pod was neither replaced nor +restarted. ## Milestone 5: Tag Resolution & Pull Secrets diff --git a/hack/sync-helm.sh b/hack/sync-helm.sh index f55df976..3dd382ee 100755 --- a/hack/sync-helm.sh +++ b/hack/sync-helm.sh @@ -92,6 +92,13 @@ sed -i '/^ image: {{ include "bootc-operator\.image" \. }}/i\ gen_workload "$CONFIG_DIR/daemon/daemon.yaml" DaemonSet daemon \ "$CHART_DIR/templates/daemon-daemonset.yaml" + +# Inject optional daemon.extraArgs without changing the default daemon arguments. +sed -i '/^ image: {{ include "bootc-operator\.image" \. }}/i\ + {{- with .Values.daemon.extraArgs }}\ + args:\ + {{- toYaml . | nindent 12 }}\ + {{- end }}' "$CHART_DIR/templates/daemon-daemonset.yaml" echo "Synced workloads" # --- Bindings & ServiceAccounts --- diff --git a/test/e2e/upgrade_test.go b/test/e2e/upgrade_test.go index 957539b1..df31cf4c 100644 --- a/test/e2e/upgrade_test.go +++ b/test/e2e/upgrade_test.go @@ -223,7 +223,7 @@ func helmUpgradeOperator(t *testing.T, chartPath, img string, extraArgs []string helmRun(t, args...) } -// helmImageArgs returns the --set flags for image and controller args. +// helmImageArgs returns the --set flags for image and E2E operator args. // An empty img keeps the image of the chart. func helmImageArgs(t *testing.T, img string, extraArgs []string) []string { t.Helper() @@ -238,6 +238,9 @@ func helmImageArgs(t *testing.T, img string, extraArgs []string) []string { if secret := os.Getenv("E2E_IMAGE_PULL_SECRET"); secret != "" { args = append(args, "--set", "imagePullSecrets[0].name="+secret) } + if interval := os.Getenv("E2E_BOOTC_POLL_INTERVAL"); interval != "" { + args = append(args, "--set-string", "daemon.extraArgs[0]=--bootc-poll-interval="+interval) + } if len(extraArgs) > 0 { j, err := json.Marshal(extraArgs) if err != nil { diff --git a/test/e2e/watcher_test.go b/test/e2e/watcher_test.go index 7da9835c..5c051b97 100644 --- a/test/e2e/watcher_test.go +++ b/test/e2e/watcher_test.go @@ -15,7 +15,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" bootcv1alpha1 "github.com/bootc-dev/bootc-operator/api/v1alpha1" - "github.com/bootc-dev/bootc-operator/internal/daemon" "github.com/bootc-dev/bootc-operator/test/e2e/e2eutil" testutil "github.com/bootc-dev/bootc-operator/test/util" ) @@ -29,6 +28,11 @@ func TestExternalBootcStatusChange(t *testing.T) { g := NewWithT(t) g.SetDefaultEventuallyTimeout(pollTimeout) g.SetDefaultEventuallyPollingInterval(pollInterval) + // Match the interval configured by deploy-bink and preserved by the upgrade test. + pollIntervalArg := os.Getenv("E2E_BOOTC_POLL_INTERVAL") + fallbackPollInterval, err := time.ParseDuration(pollIntervalArg) + g.Expect(err).NotTo(HaveOccurred(), "E2E_BOOTC_POLL_INTERVAL must be a duration") + g.Expect(fallbackPollInterval).To(BeNumerically(">", 0)) env := e2eutil.New(t) workerCreationStart := time.Now() nodeName := env.AddNode(t) @@ -57,8 +61,8 @@ func TestExternalBootcStatusChange(t *testing.T) { g.Expect(pod.Status.ContainerStatuses).To(ConsistOf(HaveField("RestartCount", int32(0)))) g.Expect(pod.Spec.Containers).To(HaveLen(1)) g.Expect(append(pod.Spec.Containers[0].Command, pod.Spec.Containers[0].Args...)). - NotTo(ContainElement(ContainSubstring("bootc-poll-interval")), - "the test relies on the daemon's default polling interval") + To(ContainElement("--bootc-poll-interval="+pollIntervalArg), + "the test relies on the configured E2E polling interval") const stageUnit = "bootc-e2e-external-switch.service" // Killing kubectl does not stop a transient service on the host. Run this @@ -101,7 +105,7 @@ func TestExternalBootcStatusChange(t *testing.T) { "bootc", "switch", env.NodeImageUpdateDigestedPullSpec(), ) - // This is well below the daemon's default fallback polling interval. + // This is well below the configured E2E fallback polling interval. observationCtx, cancelObservation := context.WithTimeout(ctx, 30*time.Second) defer cancelObservation() testutil.WaitForNodeIdle(t, g, observationCtx, env.Client, nodeName, 30*time.Second, @@ -111,10 +115,10 @@ func TestExternalBootcStatusChange(t *testing.T) { g.Expect(observationCtx.Err()). NotTo(HaveOccurred(), "status must be observed within 30 seconds") - // Fallback polling waits for the default interval after watcher startup. + // Fallback polling waits for the configured interval after watcher startup. // This bound starts before the worker exists and uses the runner's clock, // so a fallback poll cannot account for the observed status update. - g.Expect(time.Since(workerCreationStart)).To(BeNumerically("<", daemon.DefaultPollInterval), + g.Expect(time.Since(workerCreationStart)).To(BeNumerically("<", fallbackPollInterval), "status must be observed before the first possible fallback poll") var after bootcv1alpha1.BootcNode From eefdb4478511a1f4bd2eb01ca301dd79b0c303d9 Mon Sep 17 00:00:00 2001 From: HarshwardhanPatil07 Date: Tue, 3 Nov 2026 10:43:05 +0530 Subject: [PATCH 2/2] ci: allow setup time beyond the E2E test timeout The Kubernetes 1.35 E2E job exceeded its 75-minute limit while tests were still running. Allow 105 minutes for the job so the existing 90-minute Go test timeout has room for cluster setup and cleanup. Assisted-by: AI Signed-off-by: HarshwardhanPatil07 --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 428e5e91..e4789da4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,7 +63,7 @@ jobs: name: e2e (${{ matrix.kube-minor }}) runs-on: ubuntu-latest needs: [build-bink, build-operator] - timeout-minutes: 75 + timeout-minutes: 105 permissions: contents: read strategy: