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: 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