feat(e2e): consolidated E2E optimization changes (cozyreport + helmrelease-interval) - #2500
Closed
myasnikovdaniil wants to merge 29 commits into
Closed
feat(e2e): consolidated E2E optimization changes (cozyreport + helmrelease-interval)#2500myasnikovdaniil wants to merge 29 commits into
myasnikovdaniil wants to merge 29 commits into
Conversation
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…t context The previous report omitted everything needed to diagnose failures that originate deeper than a single system package install — Flux controller logs, the actual rendered Helm manifest from the storage secret, recent events, cert-manager state, the cozystack-operator's own logs, and sandbox host context (talosctl logs, dmesg, disk). This makes failures self-diagnosable from cozyreport.tgz alone, which is the only artifact that survives a failed E2E run. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
A 50MB tarball with 200 directories is opaque on first contact. summary.txt at the root surfaces 'what is broken right now' — non-Ready HRs, ImagePullBackOff pods, OOMKilled events, cert-manager state, Flux Source health, storage binding, node pressure — so triage starts with one file instead of a directory tree dive. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…0s in E2E Adds --helmrelease-interval (default 5m, matching today's behaviour) to the operator and a corresponding cozystackOperator.helmReleaseInterval value on the cozy-installer chart (default empty -> no flag rendered -> operator default 5m applies). E2E install sets it to 30s. Rationale: with the 5m default, dependency- blocked HRs (waiting on cert-manager webhooks, CRDs) are requeued only every 5 min, producing an 8-10 min dead zone in the E2E install where the fast pack of HRs is Ready but the slow tail hasn't been retried yet. 30s collapses the gap. Production defaults are unchanged. The override is opt-in per install. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…tall Previously these three configuration blocks ran as separate @test cases after the 15m platform HR wait, adding ~1.5 min sequentially. None of them gates platform HR reconcile, so they can run as a background prep that completes during the main wait. The helper script polls for its own prerequisites and is awaited at the end of the Install Cozystack test so failures still surface deterministically. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…yment The previous 5-min timeout on `until kubectl get deploy/linstor-controller` fired during cold-install runs where the LINSTOR HR's dependency chain (cert-manager → piraeus-operator-crds → piraeus-operator → linstor) takes longer than 5 min to resolve, killing the whole post-install-prep script via `set -eu`. With CI's 3x retry the failure was hidden; with the retry removed it would surface every cold install. Wait on the LINSTOR HR being Ready first (the actual prerequisite), then keep the existence-poll as a near-instant backstop, then wait for Available. Same final semantics, no fragile fixed-window timeout. Surfaced by PR #2500 cozyreport diagnostics (post-install-prep.log). Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Across 5 sampled failure runs (30 PR runs total), 25/25 retry attempts failed. The retry loop never recovered a flake — it only stretched deterministic failures (e.g. kubernetes-previous: 11:37 + 7:49 + 10:16 = 29:42 wasted on one broken test). Replace with a single attempt plus inline diagnostics (HR list + recent events) so the actual failure surfaces immediately. Re-runs remain available via the standard 'gh run rerun' / empty-commit retry path. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
A failed cluster bootstrap or platform install is almost always a real bug (Talos boot, chart, operator) — not a flake. The 3x retry on these steps was the direct cause of the 250-min outlier run (24931612182): each retry compounds 25-30 min of work, and the failure mode persists across attempts. Single attempt + the existing 'collect-report' step on always() gives faster signal and a debug archive. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Prepare environment is pure infrastructure (Talos image download, sandbox VMs boot, network setup). Failures here are mostly noisy-runner / transient infra hiccups (image-download stalls, NIC negotiation, etc.) — not cozystack code under test. Retry-on-failure is the right policy for infra setup. Install Cozystack and Run E2E tests remain single-attempt: they exercise cozystack code, where retries hide real bugs. Refines the previous two retry-removal commits. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The app bats files all share the pattern:
kubectl apply -f - <<EOF ... EOF
sleep 5
kubectl wait hr <name> --for=condition=ready
The fixed sleep guesses how long the cozystack-operator needs to translate
the app CR into a HelmRelease. On a noisy CI runner or after a cold
install that wait is sometimes too short, so the immediately-following
`kubectl wait hr ...` fails because the HR object does not exist yet
(`kubectl wait` errors out instantly on a missing resource).
Replace each `sleep 5` (and the one outlier `sleep 15` in foundationdb
that was paying for the same risk) with an event-driven backstop that
polls for the HR's existence and exits the moment it appears, capped at
60s. The downstream `kubectl wait --for=condition=ready` is unchanged,
preserving the original total budget.
Same shape as the LINSTOR fix in commit eb87413 on feat/e2e-optimization:
wait for the actual prerequisite (the HelmRelease the operator creates),
not on a downstream artefact whose existence depends on it.
Files: postgres, mariadb, kafka, mongodb, clickhouse, redis, qdrant,
harbor, openbao, external-dns (both tests), vminstance (both tests, with
the existence backstop hoisted before the downstream vmi-ip poll), and
foundationdb.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…tl proxy `kubectl proxy --port=21234 & sleep 0.5` guesses how long the proxy needs to start listening. On a slow runner the curl that follows can race the proxy and fail with "connection refused". Replace the fixed `sleep 0.5` with `nc -z localhost 21234` polled until the listener accepts a connection (10s cap). Same idiom already used in hack/e2e-apps/bucket.bats for the seaweedfs-s3 port-forward. Also save the proxy PID to a named variable so the trap kills the right process even if a later background command lands first in $!. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Three poll/sleep sites in the install bats and the kubernetes runner are NOT replaceable by `kubectl wait --for=condition=...` because the signal they observe is not a Kubernetes API condition: - e2e-install-cozystack.bats:55-56 - 5s pad lets late-arriving HRs join the awk-snapshot the parallel `kubectl wait` runs against. There is no k8s condition for "all expected platform HRs have been emitted" short of hard-coding the list. - e2e-install-cozystack.bats:75 - LINSTOR node membership is reported by the linstor binary running inside the controller pod (kubectl exec), not a CRD status, so kubectl wait cannot subscribe to it. - e2e-apps/run-kubernetes.sh:231-238 - validates the external HTTP path through MetalLB -> tenant ingress -> backend pod end-to-end. Not a single API condition. Annotate each with TODO(e2e-replace-fixed-timeouts) and the rationale so future readers do not "fix" them with a kubectl wait that does not work. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil
force-pushed
the
feat/e2e-optimization
branch
from
April 27, 2026 19:07
dfd2306 to
461cdf9
Compare
The cozy-installer chart declares Namespace cozy-system itself (with helm.sh/resource-policy: keep). Combining that with --create-namespace makes Helm v3 pre-create the namespace via plain kubectl-create (without helm annotations); the subsequent chart apply then fails with 'namespaces cozy-system already exists'. The 3x retry on Install Cozystack was hiding this. First attempt failed, second saw 'release exists' and treated it as upgrade. Reproducible across PRs (PR #2507 E2E hit the same first-attempt failure today and recovered on retry). Surfaced cleanly after dropping the retry on this step. Drop --create-namespace; let the chart manage its own namespace. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The cozy-installer chart declares Namespace cozy-system itself, which
makes helm cold-install paradoxical:
- WITH --create-namespace: helm pre-creates the ns without helm
annotations; the chart's own Namespace apply then fails with
'already exists'.
- WITHOUT --create-namespace: helm fails before any apply because it
can't write its release-secret to a non-existent ns.
The 3x retry was masking both directions — second attempt saw a partial
release and took the upgrade path, which patch-merges instead of
strict-create. Reproducible on every cold install across PRs.
Pre-create cozy-system with meta.helm.sh/release-name and
app.kubernetes.io/managed-by=Helm so helm adopts it cleanly during
install. Labels mirror the chart's template (PSA enforce=privileged etc.)
so behaviour matches what the chart would have done.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…hook
Removes the chart's `Namespace cozy-system` resource and replaces it with
a pre-install/pre-upgrade Job hook (cozy-system-labeler) that patches the
required labels onto the namespace after `--create-namespace` creates it.
Why: helm v3 has a known chicken-and-egg with charts that ship their own
Namespace:
- WITH `--create-namespace` on the install command, helm pre-creates the
namespace via plain kubectl-create (no helm meta annotations); the
chart's own Namespace apply then fails with `already exists`.
- WITHOUT `--create-namespace`, helm fails immediately because it cannot
write its release-secret to a non-existent namespace.
Until now this was hidden by the 3x retry on `Install Cozystack` in
`.github/workflows/pull-requests.yaml`: first attempt always fails with
the conflict, second attempt sees the existing failed release and takes
the upgrade code path which patch-merges instead of strict-create.
Reproducible on every cold install. Surfaced cleanly when retries on the
install step were dropped.
After this change:
- install commands use `helm upgrade --install --namespace cozy-system
--create-namespace`. Standard pattern, matches kube-prometheus-stack /
argo-cd / cert-manager / others.
- the pre-install hook (SA + ClusterRole + ClusterRoleBinding + Job)
patches `cozystack.io/system=true` and
`pod-security.kubernetes.io/enforce=privileged` onto the namespace
before main resources apply.
- hook-delete-policy=before-hook-creation,hook-succeeded so the RBAC
surface only exists during install/upgrade.
Verified end-to-end on a kind cluster: cold install in 3.3s, upgrade
idempotent, cleanup clean. Image pinned to `alpine/k8s:1.32.0` for the
hook (small, public, includes kubectl).
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Now that the chart fix (this same branch) bootstraps cozy-system via --create-namespace + a pre-install label hook, the bats no longer needs to pre-apply a hand-crafted helm-adoptable namespace. Restore plain --create-namespace and drop the kubectl apply block. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…y timeouts Two reliability fixes in `hack/e2e-apps/vminstance.bats`: 1. Delete-recreate race. Both @test blocks deleted the prior VMInstance/VMDisk with `kubectl ... --timeout=2m || true`. The `|| true` silently swallowed timeout errors, letting the test continue with the resource still in finalizer-drain. The next `kubectl apply` then no-ops with "Detected changes to resource ... which is currently being deleted", producing a NotFound on the downstream HR wait. Bumped the delete timeout to 3m and removed `|| true` so true delete failures surface loudly. End-of-test cleanup deletes also got the explicit timeout. 2. VM IP and VM ready timeouts. The 20s timeout for the VMI to acquire an IP was unrealistic for nested KubeVirt under runner load — virt-launcher + libvirt + cloud-init DHCP routinely takes 30-60s. Bumped to 120s with a 2s poll interval (60 polls instead of 4). VM ready timeout bumped from 20s to 60s for the same reason. Both surfaced as recurring first-attempt failures in the past 30 successful PR runs (7/17 analysable runs in each case) — masked by the 3x retry on `Run E2E tests`. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… with configurable RetryInterval
The operator generated HelmReleases with `Install.Remediation{Retries: -1}`
and `Upgrade.Remediation{Retries: -1}`, which is the v2 way of saying
"retry forever, never remediate". Combined with the spec-level
`Interval: 5m`, this meant a failed install/upgrade waited a full 5
minutes before the next attempt — even when the underlying race that
caused the failure (e.g. a chart artifact not quite Ready, a HR
dependency reconciling out of order) cleared in seconds.
Audit of the last 30 successful PR runs found that every single run had
its `Install Cozystack` step's `kubectl wait hr/seaweedfs-system` time
out at the 2-minute mark and recover only after an inline
`flux reconcile --force` workaround. Same root cause: 5-minute retry
interval was longer than the test's wait window. That workaround can be
removed once this change lands.
Switch to `Strategy.Name=RetryOnFailure` with `RetryInterval` exposed via
a new `--helmrelease-retry-interval` operator flag (default 30s):
- Functionally equivalent to the previous configuration ("retry forever
on failure") since `Retries: -1` meant remediation never fired anyway.
- Decouples retry-on-failure timing from `spec.Interval` so failed
releases recover at 30s without polling healthy releases at the same
cadence (which would multiply controller load by ~10x for no benefit).
- Helm chart exposes `cozystackOperator.helmReleaseRetryInterval`,
defaulting to empty (operator uses its own 30s default).
- Same flag pattern as the existing `--helmrelease-interval` (5m default),
also added in this PR via cherry-pick.
Verified: go build green, go test ./internal/operator/... pass, helm
template renders both flags conditionally on values.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The HelmRelease generation in package_reconciler hardcoded three values that have caused recurring pain: - Install.Timeout = 10m (line 214) - Upgrade.Timeout = 10m (line 220) - MaxHistory = unset (Helm default 5) The 10m timeout in particular contradicts the per-Application timeout work done in `pkg/config/config.go` and `pkg/registry/apps/application/rest.go` (commit 7b146cb), which lets individual Applications override the install/upgrade timeout via annotation. The PackageSource side had no equivalent — etcd works around it by hand-rolling its HR YAML in `packages/apps/tenant/templates/etcd.yaml` to set a 30m timeout, harbor has had multiple commits chasing the same problem from the other side. This closes that gap. New operator flags (each maps to a chart value with empty default; operator uses its own default when value is empty): - `--helmrelease-install-timeout` (default 10m) → Spec.Install.Timeout - `--helmrelease-upgrade-timeout` (default 10m) → Spec.Upgrade.Timeout - `--helmrelease-max-history` (default 5) → Spec.MaxHistory Production behaviour unchanged. E2E and edge cases (cert rotation, slow-installing charts) can override per-cluster without modifying chart manifests. Per-component overrides on `ComponentInstall` (mirroring the existing `UpgradeCRDs` pattern) would be a strictly better fix for charts like etcd that need an outlier timeout — left as a deliberate followup since it touches the Package CR API. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…ule nodes The pre-install Job runs before any CNI is installed, so all nodes are tainted NotReady-NoSchedule and the pod has no pod network. Without adjustment the Job's pod sits Pending until helm's pre-install timeout fires, blocking the entire install. Surfaced in PR #2500 CI run 25040584552 attempt 2: FailedScheduling 0/3 nodes are available: 3 node(s) had untolerated taint {node.kubernetes.io/not-ready: }. Mirror the cozystack-operator deployment's scheduling pattern: - hostNetwork=true so the pod doesn't depend on CNI - Tolerations matching the operator (not-ready / unreachable / cilium.agent-not-ready / cloudprovider.uninitialized) - Variant-aware KUBERNETES_SERVICE_HOST/PORT env so kubectl reaches the apiserver before kube-proxy + CNI are up: talos: localhost:7445 (KubePrism) generic: cozystack.apiServerHost / Port hosted: default in-cluster Verified on the sandbox kind cluster that the rendered Job manifest schedules and the kubectl container starts (the kind cluster itself doesn't run KubePrism so the env-var path can't be end-to-end-tested there; the Talos E2E will exercise it). Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil
added a commit
that referenced
this pull request
Apr 28, 2026
…ule nodes The pre-install Job runs before any CNI is installed, so all nodes are tainted NotReady-NoSchedule and the pod has no pod network. Without adjustment the Job's pod sits Pending until helm's pre-install timeout fires, blocking the entire install. Surfaced in PR #2500 CI run 25040584552 attempt 2: FailedScheduling 0/3 nodes are available: 3 node(s) had untolerated taint {node.kubernetes.io/not-ready: }. Mirror the cozystack-operator deployment's scheduling pattern: - hostNetwork=true so the pod doesn't depend on CNI - Tolerations matching the operator (not-ready / unreachable / cilium.agent-not-ready / cloudprovider.uninitialized) - Variant-aware KUBERNETES_SERVICE_HOST/PORT env so kubectl reaches the apiserver before kube-proxy + CNI are up: talos: localhost:7445 (KubePrism) generic: cozystack.apiServerHost / Port hosted: default in-cluster Verified on the sandbox kind cluster that the rendered Job manifest schedules and the kubectl container starts (the kind cluster itself doesn't run KubePrism so the env-var path can't be end-to-end-tested there; the Talos E2E will exercise it). Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…espace The pre-install Job approach (added in c0b76b1, b46151a) hits a fatal chicken-and-egg on Talos clusters that enforce PodSecurity baseline:latest on bare namespaces: pods "cozy-system-labeler-..." is forbidden: violates PodSecurity "baseline:latest": host namespaces (hostNetwork=true) The labeler needs hostNetwork=true (no CNI yet) which requires the namespace to be PSA-privileged, but the labeler's whole job is to add that label. Cannot work from inside the namespace it's labeling. Drop the labeler Job + ServiceAccount + ClusterRole + ClusterRoleBinding. The chart now only removes the chart-defined Namespace and assumes the caller pre-creates cozy-system with the right labels — same pattern as kube-prometheus-stack, argo-cd, cert-manager, and other mainstream charts. Install procedure becomes two-step: kubectl apply -f - <<EOF apiVersion: v1 kind: Namespace metadata: name: cozy-system labels: cozystack.io/system: "true" cozystack.io/deletion-protected: "true" pod-security.kubernetes.io/enforce: privileged EOF helm upgrade installer packages/core/installer \ --install --namespace cozy-system ... The cozystack-operator pod (hostNetwork=true) is then admitted because the namespace already has enforce=privileged. Verified end-to-end on a kind cluster — clean install in 0.1s, no hooks involved, no PSA conflict, no retry needed. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The chart fix (3a87485, be0a668) removes the chart-defined Namespace without replacing it. On the Talos E2E cluster the apiserver enforces PodSecurity baseline-by-default on bare namespaces, so the cozystack- operator pod (hostNetwork=true) is rejected if cozy-system is created without enforce=privileged. Apply the namespace from the bats with the labels the operator depends on, then run helm install without --create-namespace. Mirrors the documented production install procedure for the new chart shape. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
`kubectl wait <kind> <name>` errors immediately with NotFound if the resource doesn't exist yet — even with --for=condition or --for=jsonpath. The redis test in CI run 25043350705 failed in 1 second for exactly this reason: the redis-failover operator hadn't created the PVC by the time the test waited for it. Previously the 3x retry on `Run E2E tests` masked this race; with retry dropped, every such call is a flake risk. Add a small `until kubectl get` existence backstop before each kubectl wait, matching the pattern already established for HRs in commit 66888c9. 33 backstops across 12 files: bucket.bats — bucketclaims, bucketaccesses x2 clickhouse.bats — statefulset 0-0 (0-1 already covered) harbor.bats — deploy x3, bucketclaims, bucketaccesses kafka.bats — kafkas mariadb.bats — statefulset, deployment mongodb.bats — statefulset openbao.bats — sts, pvc postgres.bats — job.batch qdrant.bats — sts, pvc redis.bats — pvc, deploy, sts (the trigger) vminstance.bats — dv, pvc, vm (with 120s for KubeVirt latency) e2e-install-cozystack.bats — apiservices, sts/etcd, vmalert, vmalertmanager, vlclusters, vmcluster, clusters.postgresql.cnpg.io, deploy/grafana-deployment, namespace Same pattern, same 60s default timeout for the existence wait (120s for nested-virt resources). Once the resource exists, the original wait timeout takes over. Run-kubernetes.sh has the same race shape on several waits (nfs, kamaji, machinedeployment, etc.) — out of scope here; flagged for follow-up. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Two bugs caused the harbor E2E to fail (CI #25049445125): 1. cozy-harbor's bucket-secret.yaml called `index $existingSecret.data "BucketInfo"` unconditionally. During first-time install the COSI BucketAccess controller may create the credentials Secret as a placeholder before populating it, so `.data` is nil and the chart render crashes with `index of untyped nil`. 2. The downstream `<release>-system` HelmRelease starts reconciling immediately, in parallel with bucket provisioning, hitting bug #1 on every retry within the test's 5-minute window. The previous shape relied on `lookup` to read the Secret at render time. helm-controller's upgrade trigger is digest-based over composed values + chart artifact, so a `lookup` returning new data on a later reconcile is not enough on its own to force an upgrade — the rendered output may diverge but the digest does not. Switch to a values-driven shape: - `bucket-secret.yaml` now reads `.Values.bucket.bucketInfo` (a JSON string) and uses `dig` for safe access; if the value is empty or the expected nested fields are missing, no `*-registry-s3` Secret is rendered. - The `<release>-system` HR sources `BucketInfo` via `valuesFrom` with `targetPath: bucket.bucketInfo`. With the default `optional: false`, helm-controller will refuse to compose values until the key exists, which both gates initial reconciliation on the BucketAccess Secret being populated and forces a config-digest change (and thus a helm upgrade) when its contents change. - `bucket.secretName` is removed from the system chart's values and from the apps chart's `values:` block; the only consumer (the `lookup` call) is gone. Verified four scenarios via `helm template` against the system chart: unset bucketInfo, empty string, empty JSON object `{}`, and a fully-populated BucketInfo — only the last renders the registry-s3 Secret; the others render nothing without erroring. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… BucketAccess reconcile Upstream COSI v0.2.2's BucketAccess reconciler does a Get->mutate->Update on the parent Bucket and surfaces "Operation cannot be fulfilled ... the object has been modified" as a FailedGrantAccess event when it races against the Bucket reconciler in the same controller process. Wrap the mutation in retry.RetryOnConflict so the reconcile loop refreshes and retries instead of leaking the conflict to users. Carried as 91-bucketaccess-conflict-retry.diff until upstreamed (cf. 89-reconciliation.diff and 90-bucket-name.diff, both dropped in c29d501 once merged upstream). Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Some workloads (OVN raft, LINSTOR controller) fail when replicas start at different times due to image-pull stagger across nodes. Add a DaemonSet-based pre-pull step that runs before helm install, ensuring all nodes have the images cached so every replica starts within milliseconds of each other. Covers kube-ovn (confirmed OVN raft race), piraeus-server, and linstor-csi. Comments in hack/e2e-prepull-images.sh point to the source chart values so versions stay in sync. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Some workloads (OVN raft, LINSTOR controller) fail when replicas start at different times due to image-pull stagger across nodes. Add a DaemonSet-based pre-pull step that runs before helm install, ensuring all nodes have the images cached so every replica starts within milliseconds of each other. The script accepts image refs on stdin and creates one container per image (parallel pulls, total time = max of any single image rather than sum). The bats test sources images directly from the rendered charts via yq, walking only PodSpec-shaped objects so version bumps stay in sync automatically without a separate hardcoded list. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil
force-pushed
the
feat/e2e-optimization
branch
from
April 29, 2026 11:18
ffd5e4b to
0fc61ee
Compare
The previous fix (a21c18f) sourced BucketInfo via Flux valuesFrom with `targetPath: bucket.bucketInfo`. Flux runs values with `targetPath` through Helm's `strvals.ParseInto`, which splits the value on commas as list separators. The COSI BucketInfo JSON is comma-rich, so values resolution bailed: could not resolve Secret chart values reference 'tenant-X/harbor-X-registry-bucket' with key 'BucketInfo': key "\"spec\":{\"bucketName\":\"bucket-1785...\"" has no value (cannot end with ,) Drop `targetPath`. With only `valuesKey: BucketInfo`, helm-controller unmarshals the value as YAML and merges at the chart's values root, so JSON commas stay nested instead of being split. The system chart's bucket-secret.yaml now reads `.Values.spec.bucketName` / `.Values.spec.secretS3.{accessKeyID,accessSecretKey,endpoint}`, guarded by nested `with` blocks so the registry-s3 Secret renders only when secretS3 has been populated. Gating semantics from the previous fix are preserved: with the default `optional: false`, helm-controller still refuses to compose values until the COSI BucketAccess controller writes `BucketInfo` into the Secret, and the value's content still drives the HR config-digest. Verified via `helm template` against the system chart with three scenarios: missing `spec` and `spec` without `secretS3` render nothing; full BucketInfo renders all five S3 env keys correctly. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: Claude <noreply@anthropic.com>
myasnikovdaniil
added a commit
that referenced
this pull request
May 2, 2026
…hook
The chart used to ship `Namespace cozy-system` directly, which trips a
helm v3 chicken-and-egg whenever the install command uses
`--create-namespace`:
- WITH `--create-namespace`, helm pre-creates the namespace via plain
kubectl-create (no helm meta annotations); the chart's own Namespace
apply then fails with `already exists`.
- WITHOUT `--create-namespace`, helm fails immediately because it
cannot write its release-secret to a non-existent namespace.
The bug was hidden by the 3x retry on the install step in
`.github/workflows/pull-requests.yaml`: attempt 1 fails with the
conflict, attempt 2 sees the failed release and takes the upgrade code
path (patch-merge instead of strict-create). Surfaces cleanly when the
retry is dropped; reproducible on every cold install.
Fix has two pieces:
1. Drop the chart-side `Namespace cozy-system` resource. Install
commands now use the standard `helm upgrade --install --namespace
cozy-system --create-namespace` (matches kube-prometheus-stack /
argo-cd / cert-manager).
2. Add a pre-install/pre-upgrade Job hook (`cozy-system-labeler`) that
patches `cozystack.io/system=true` and
`pod-security.kubernetes.io/enforce=privileged` onto the namespace
so the cozystack-operator pod (hostNetwork=true) is admitted.
The hook runs in `kube-system`, not `cozy-system`. Reason: the operator
pod needs hostNetwork=true (no CNI yet at install time), which violates
PodSecurity baseline. cozy-system therefore needs enforce=privileged
*before* the operator pod is admitted. A labeler pod inside cozy-system
would itself need hostNetwork=true and hit the same admission denial:
pods "cozy-system-labeler-..." is forbidden: violates PodSecurity
"baseline:latest": host namespaces (hostNetwork=true)
Job admission denial means no pod is ever created, .status.failed never
increments and backoffLimit never trips. helm's --wait waits for
Complete=True forever and surfaces only the generic "timed out waiting
for the condition" at its budget.
`kube-system` is PSA-exempt on Talos
(`defaults.exemptions.namespaces` in the apiserver
PodSecurityConfiguration) and unlabeled on vanilla kubeadm/kind/k3s, so
hostNetwork pods are admitted there. The hook's ServiceAccount is bound
to a ClusterRole scoped via `resourceNames: ["cozy-system"]` to the one
target namespace, keeping the cluster permission grant minimal.
`hook-delete-policy: before-hook-creation,hook-succeeded` ensures the
SA, RBAC, and Job are cleaned up after a successful install/upgrade.
Verified end-to-end in CI on the parallel consolidation branch
feat/e2e-optimization (PR #2500): cold install passes admission, the
operator pod comes up, all downstream E2E tests pass.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Re-introduces the pre-install/pre-upgrade Job hook (cozy-system-labeler) that was added by 3a87485/4754f57f and dropped by be0a668, but moves it into kube-system to dodge the PSA admission denial that broke the in-namespace approach: pods "cozy-system-labeler-..." is forbidden: violates PodSecurity "baseline:latest": host namespaces (hostNetwork=true) The operator pod needs hostNetwork=true (no CNI yet at install time), which violates PodSecurity baseline. cozy-system therefore needs enforce=privileged before the operator pod can be admitted. A labeler pod inside cozy-system would itself need hostNetwork=true and hit the same admission denial — Job admission denial means no pod is ever created, .status.failed never increments and backoffLimit never trips, helm waits for Complete=True until its budget elapses. `kube-system` is PSA-exempt on Talos (defaults.exemptions.namespaces in the apiserver PodSecurityConfiguration) and unlabeled on vanilla kubeadm/kind/k3s, so hostNetwork pods are admitted there. The hook's ServiceAccount is bound to a ClusterRole scoped via `resourceNames: ["cozy-system"]` to the one target namespace, keeping the cluster permission grant minimal. Also revert the e2e bats workaround (ddc429c) that pre-applied cozy-system manually before helm install: the labeler hook handles the labels in-chart now, so the bats can go back to plain `helm upgrade --install --namespace cozy-system --create-namespace ...`. Mirrors the corresponding fix on PR #2508 (fix/installer-namespace-bootstrap) so the two branches converge on the same resolution. Verified end-to-end in CI on this branch: cold install passes admission, operator pod comes up, all downstream E2E tests pass. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil
force-pushed
the
feat/e2e-optimization
branch
from
May 2, 2026 12:49
51b3563 to
d3e3677
Compare
This was referenced May 2, 2026
The previous run on d3e3677 raced PR #2508's concurrent build for `backupstrategy-controller:latest` and lost: ERROR: failed to build: unknown: Conflicted with another upload of the same manifest. Unrelated to the chart change at HEAD — pure shared-registry collision on :latest. Empty commit to retrigger the workflow. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil
added a commit
that referenced
this pull request
May 4, 2026
…#2556) ## What this PR does Some workloads (kube-ovn raft, LINSTOR controller / piraeus-server) fail when replicas start at materially different times due to image-pull stagger across nodes. Adds a DaemonSet-based pre-pull step that runs before `helm install`, ensuring every node has the images cached so replicas start within milliseconds of each other. `hack/e2e-prepull-images.sh` accepts image refs on stdin and creates one container per image so pulls run in parallel — total time = max(any single image) rather than sum. The bats test sources image refs directly from the rendered platform charts via `yq`, walking only PodSpec-shaped objects, so version bumps stay in sync without a hardcoded list. Surfaced from #2500. ### Release note ``` NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added an end-to-end test that pre-pulls platform container images before the installation workflow to improve install reliability. * **Chores** * Added an automated pre-pull utility that reads image lists, deduplicates and sorts them, deploys a short-lived pre-puller across nodes, waits for completion, then cleans up. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil
added a commit
that referenced
this pull request
May 4, 2026
#2553) ## What this PR does Makes `cozyreport.tgz` actually useful when triaging an E2E failure deeper than a single pod log. Adds: - Flux controller logs (helm-controller, source-controller, notification-controller, kustomize-controller, last 2000 lines each) - Flux source resources (`HelmRepository`, `OCIRepository`, `GitRepository`, `ExternalArtifact`) - Decoded Helm storage secrets for non-Ready HRs (`base64 -d | base64 -d | gzip -d`) - Cluster events (all + warning-only filtered file) - cert-manager `Certificate` / `CertificateRequest` / `Order` / `Challenge` resources + cert-manager logs - `cozystack-operator` deployment logs (current + previous) - `Application` / `ApplicationDefinition` / `Tenant` resources - Sandbox host context per node: `df`, `free`, `ps`, `dmesg`, `talosctl logs/dmesg/kubelet/containerd` New executable `hack/cozyreport-summary.sh` writes a `summary.txt` at the archive root listing what is broken right now — the first thing to read when downloading the artifact from a CI failure. Surfaced from #2500. ### Release note ``` NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a timestamped diagnostic summary report that highlights failing resources and key cluster issues. * Expanded diagnostic collection: controller logs (current+previous), Flux source status, cert-manager certificate details, Helm release payloads, and Cozystack resource status. * Added host-level diagnostics (disk/memory/process/dmesg) and improved event/warning listings to aid troubleshooting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil
added a commit
that referenced
this pull request
May 6, 2026
## What this PR does Replaces fragile `sleep N` / `timeout N until …; do sleep …; done` patterns in `hack/*.bats` and `hack/*.sh` with `kubectl wait --for=condition=…` plus a small existence backstop: ```sh until kubectl get <resource> >/dev/null 2>&1; do sleep 2; done kubectl wait <resource> --for=condition=Ready --timeout=… ``` ### Why the existence backstop matters `kubectl wait` against a not-yet-created resource sits silently for the full timeout and then fails with `error: timed out waiting for the condition` — useless for diagnosis. With the backstop, the test fails fast with a clear "resource never appeared" error. Existence backstop added at 14 sites in this sweep across: - `redis`, `kafka`, `openbao`, `harbor`, `mongodb`, `qdrant`, `clickhouse`, `mariadb`, `postgres`, `bucket`, `external-dns`, `foundationdb` bats - `hack/e2e-install-cozystack.bats` - `hack/e2e-apps/run-kubernetes.sh` - `hack/e2e-test-openapi.bats` (port-readiness wait replacing fixed `sleep 5` after `kubectl proxy`) The vm-disk hunk in `hack/e2e-apps/vminstance.bats` is included here; the vm-instance hunks land separately via the `daniil/split-vminstance` PR. ### Genuine sleeps annotated The 3 remaining genuine sleeps (LINSTOR node membership propagation, MetalLB end-to-end HTTP path, HR-count emission heuristic) are annotated with `# TODO` + rationale so a future reader knows they are intentional. Same green-path semantics; failures now surface in seconds instead of after a 5-minute timeout. Surfaced from #2500. ### Release note ``` NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved e2e stability by replacing fixed sleeps with timeout-based polling across many integration tests, waiting for operator-created resources to exist before readiness checks to reduce race conditions and flakes. * **Documentation** * Added clarifying notes for LoadBalancer retry behavior and improved local proxy readiness checks in e2e scripts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil
added a commit
that referenced
this pull request
May 6, 2026
#2555) ## What this PR does Three fixes for `hack/e2e-apps/vminstance.bats`, surfaced once the 3x retry was dropped from `Run E2E tests`: 1. **Disk delete-recreate race.** The teardown used `kubectl delete vmdisk … || true` and immediately created a new disk; sometimes the new disk reconciled before the old finalizer completed, leaving a duplicate PVC and the new VM stuck. Drop the `|| true`, block until the old disk is gone, then create. 2. **VM IP wait timeout 20s → 120s.** Under nested-virt + concurrent runner load, a fresh VM sometimes takes 60–90 s to acquire its IP via DHCP. The 20 s bound was tripping deterministically. 3. **VM ready timeout 20s → 60s.** Same root cause; the readiness probe needs the IP to be reachable, so the lower bound has to grow alongside the IP wait. Existence backstop on the `vm-instance-$name` HR is added here too (it touches the same hunk as the timeout fix; cleaner to land them together). Surfaced from #2500. ### Release note ``` NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved end-to-end test reliability for VM instance and disk flows. * Replaced short sleeps with explicit waits for upstream and downstream deployment artifacts. * Extended polling windows and timeouts for IP assignment and readiness checks. * Strengthened cleanup to reliably remove prior VM and disk resources before and after tests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil
added a commit
that referenced
this pull request
May 7, 2026
## What this PR does ### Parallel post-install prep LINSTOR / StorageClass / MetalLB setup, previously sequential `@test` cases after the 15 m platform HR wait, now run as a background prep that completes during the platform reconcile. None of them gates platform HR reconcile, so they can move out of the critical path. New helper `hack/e2e-post-install-prep.sh` polls for its prerequisites and is awaited at the end of the `Install Cozystack` test so failures still surface deterministically. ### T2-race fix Wait on the LINSTOR HR being `Ready` before polling for `deploy/linstor-controller`. The old 5-min hard timeout fired during cold installs where the LINSTOR HR's dependency chain (cert-manager → piraeus-operator-crds → piraeus-operator → linstor) takes longer than 5 min to resolve. ### E2E uses helmReleaseInterval=30s E2E install passes `--set cozystackOperator.helmReleaseInterval=30s` to eliminate the 8–10 min Flux dead zone where dependency-blocked HRs are requeued only every 5 min. Operator-side flag and chart value landed in #2509; production defaults are unchanged. Surfaced from #2500. ### Release note ``` NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** - Improved end-to-end test initialization to run concurrent post-install preparation, wait for cluster release reconciliation, verify component readiness, and validate automated storage configuration. * **Chores** - Added background post-install setup and logging to increase test reliability and speed up cluster initialization and teardown diagnostics. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Contributor
Author
|
Child PRs are merged |
Aleksei Sviridkin (lexfrei)
added a commit
that referenced
this pull request
Jun 9, 2026
…ay.bats tenant teardown (#2558) ## What this PR does Drops the 3× retry loop on `Run E2E tests` and `Install Cozystack into sandbox`. `Prepare environment` keeps its 3× retry — that step is pure infrastructure (Talos image download, sandbox VM boot, network) where transient runner hiccups warrant a retry. On failure, the test step now captures `kubectl get hr -A -o wide` and `kubectl get events -A` under a collapsible group so triage starts with the actual broken-state snapshot. > [!NOTE] > An earlier revision of this PR also doubled every bats timeout. That commit was dropped in a rebase and is intentionally **not restored**: the timeout class that actually matters (per-app HR-Ready waits) has since been standardized at 5m on `main` (7b9f286), making a blanket 2× redundant. **Fixes gateway.bats teardown leakage.** The nested-tenant tests deleted tenants fire-and-forget, parent and child back-to-back. The leftover uninstalls (each blocked on a cleanup Job, parents wedged on still-terminating child namespaces) plus one mid-install child HR occupied exactly 5 workers on the `--concurrent=5` tenants helm-controller shard, starving whichever app test ran next — observed as the harbor HR sitting unreconciled for its whole 5m HR-Ready budget in [run 27020081550](https://github.com/cozystack/cozystack/actions/runs/27020081550), surfaced by this PR's own retry removal + diagnostics dump. Teardown now deletes child→parent with hard `wait hr --for=delete` between, so a wedged tenant uninstall fails gateway.bats itself, not an innocent neighbor. ## Why Audit of 30 successful PR runs found that across 5 sampled failure attempts, **25/25 retries** on `Run E2E tests` failed — the retry loop never recovered a flake, only stretched deterministic failures and tripled diagnostic wall-time. Same data shape on `Install Cozystack`. Beyond wasted CI time, the retry was hiding ~10 deterministic bugs (Helm namespace-ownership conflict, seaweedfs HR timeout, harbor BucketInfo wiring, vminstance disk race, etc.). Each failure looked like a "flake" because the retry sometimes coincided with whatever transient state had cleared — the retry never fixed the bug, just delayed surfacing. ## Dependencies The deterministic bugs the retry was masking are now fixed on `main`: - ✅ **#2508** — installer namespace bootstrap (Helm namespace-ownership conflict on cold install) — merged - ✅ **#2509** — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race within Flux's 5-min reconcile windows) — merged - ✅ **#2528** — harbor bucket-secret + BucketInfo gating (harbor `ValuesError` on first install) — merged - ✅ **#2529** — objectstorage-controller BucketAccess conflict retry — merged Companion PRs in the #2619 split (independent of this PR, ordering-wise): - **#2602** — Flux v2.8.0 + chart fixes - **#2601** — seaweedfs-system split This PR does NOT depend on #2602/#2601 — it now touches only the workflow file and gateway.bats teardown, both on top of fresh `main`. Surfaced from #2500. ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * CI prepare-environment step now reports plain attempt counts with clear success/failure messages. * Install and per-app test steps no longer retry; each runs once and fails immediately on error. Failed apps log diagnostics and job proceeds to remaining apps while overall job fails. * **Tests** * End-to-end tests and install/prepare flows use longer, more tolerant timeouts and added existence polling to reduce flakiness and improve diagnostics. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2558?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this exists
The
Run E2E testsstep had a 3× retry loop that masked deterministic failuresas "flakes" and tripled the diagnostic wall-time. Audit of 30 successful PR
runs found that across 5 sampled failure attempts, 25/25 retries failed —
the retry loop never once recovered a flake. It only stretched deterministic
bugs.
This branch removes the retry loops, then walks through every test step that
relied on the retry to mask a deterministic problem and either (a) fixes the
underlying bug in a focused child PR or (b) tightens the test so failures
surface as a clear error instead of a five-minute timeout.
What was done to stabilize the test suite
1. Drop CI retry loops on
Run E2E testsandInstall CozystackSingle attempt + diagnostics on failure (HR list + cluster events) under a
collapsible group.
Prepare environmentkeeps its 3× retry — that step ispure infrastructure (Talos image download, sandbox VM boot, network) where
transient runner hiccups warrant a retry. (
.github/workflows/pull-requests.yaml)2. Replace fixed timeouts with event-driven backstops
14
sleep N/timeout N until …; do sleep …; donepatterns inhack/*.batsand
hack/*.shreplaced withkubectl wait --for=condition=…plus a smallexistence backstop. The 3 genuine sleeps that remain (LINSTOR node membership
propagation, MetalLB end-to-end HTTP path, HR-count emission heuristic) are
annotated with TODO + rationale so the next reader knows they were
intentional.
3. Add
kubectl waitexistence backstops — 33 sites, 12 bats fileskubectl waitagainst a resource that doesn't exist yet sits silently for thefull timeout and then fails with
error: timed out waiting for the condition— useless for diagnosis. Wrapping each
kubectl waitwith a shortuntil kubectl get … >/dev/null 2>&1; do sleep 2; doneexistence loop surfaces thereal error: the resource never appeared. 33 sites updated across redis,
kafka, openbao, harbor, mongodb, qdrant, vminstance, clickhouse, mariadb,
postgres, bucket, and the install bats.
4. Fix the deterministic bugs the retry was hiding
The audit catalogued 10 bugs that the retry pattern was masking. Each one is
now landing in a focused child PR:
Namespace; caller pre-applies with PSA labelsseaweedfs-systemHRkubectl wait2-min timeout (no progress between Flux's 5-min reconcile windows)Strategy=RetryOnFailure+RetryInterval=30svminstanceVMDisk delete-recreate race|| trueon delete; block until removal completeslinstor-controllerdeployment not-found racePrepare environmentretains 3× retryredis kubectl wait pvcrace (NotFound)harbor— bucket-secret template fails on nil.data8daf4952— values-driven shapeharbor-test-systemHR fires before bucket secret populatedvaluesFromwithoptional: falsegates the HRharborvaluesFrom+targetPathtriggered Helmstrvalscomma-split on the BucketInfo JSON2555a9ac— droptargetPath, merge BucketInfo at the values rootBucketAccesscontroller "object has been modified" race5. Richer
cozyreportfor when something does failhack/cozyreport.shnow collects:HelmRepository,OCIRepository,GitRepository,ExternalArtifact)base64 -d | base64 -d | gzip -d)cozystack-operatordeployment logs (current + previous)Application/ApplicationDefinition/Tenantresourcesdf,free,ps,dmesg,talosctl logs/dmesg/kubelet/containerd)New
hack/cozyreport-summary.shwrites asummary.txtat the archive rootlisting what is broken right now — first thing to read when triaging an
artifact.
6. Configurable HelmRelease
Interval+ parallel install prep (companion to #2509)Eliminates the 8–10 min Flux dead zone in
Install Cozystackby letting theE2E installer override the default 5-min reconcile interval to 30s. The
install bats also runs
hack/e2e-post-install-prep.sh(LINSTOR pool +StorageClass + MetalLB pool) in the background, in parallel with the platform
reconcile wait. Production default unchanged. Operator-side flag landing in
#2509.
Stage 1 — child PRs
These hold the actual fixes and should land first. File-disjoint, mergeable in
any order. Each landing one removes its cherry-pick from this branch on rebase.
installer: drop chart-definedNamespace; caller pre-appliescozy-systemwith PSA labelscozystack-operator: 5 HelmRelease generation knobs +Strategy=RetryOnFailureharbor: bucket-secret values-driven; HR gated on BucketInfo; merge at values rootobjectstorage-controller: retry on Bucket update conflict during BucketAccess reconcileStage 2 — what's left in this PR after Stage 1
Once #2508/#2509/#2528/#2529 land and this branch is rebased, the leftovers
are sections 1, 2, 3, 5, 6 above plus the vminstance and timeout fixes
from the bug table. Likely split:
feat(cozyreport): richer archive + summary.txtci(workflow): drop retry loops on Run E2E + Install Cozystacktest(e2e): replace fixed timeouts with event-driven backstopstest(e2e): kubectl wait existence backstops across 12 bats filesfix(virtual-machine): vminstance disk race + IP/ready timeoutstest(e2e): parallel install prep alongside platform reconcileProduction safety
Intervalunchanged (5m).--helmrelease-intervalHelm value default empty → flag not rendered → operator behaviour identical for non-E2E deploys.hack/e2e-install-cozystack.bats.Release note