Addresses the branch review on the ZK->KRaft migration path. CRITICAL — broker node pool adoption. Strimzi derives broker/PVC names as <cluster>-<pool>-<id>; a pre-node-pool ZooKeeper cluster has brokers <cluster>-kafka-N, so the adopting broker pool must be named exactly "kafka" or the operator provisions fresh empty brokers and orphans the tenant's data. A new kafka.brokerPoolName helper resolves the broker pool to "kafka" for an existing (migrating) cluster and keeps the release-scoped "<release>-broker" for fresh installs, so multiple Kafka instances still coexist in one namespace. The migration hook creates the pool as "kafka" and fails closed if a "kafka" pool already belongs to another cluster (node pool names are namespace-unique, so in-place migration of co-namespaced ZK clusters is unsupported). Wired through kafkanodepools.yaml, workloadmonitor.yaml and the hook. MAJOR — migration robustness. - Raise the HelmRelease timeout: the migration Job can run tens of minutes but the release timeout defaults to 10m, so helm-controller aborted and thrashed the pre-upgrade hook. Add release.cozystack.io/helm-install-timeout=45m on the kafka ApplicationDefinition, above the Job's activeDeadlineSeconds (2400s). - Idempotent migration: do not re-issue kraft=migration once the cluster is past the ZooKeeper phase (an invalid backward transition Strimzi rejects on retry). - Fail closed on the decision gates: distinguish a genuine NotFound (skip) from any other kubectl error (abort), and never treat an empty kafkaMetadataState on an existing CR as "migrated". MINOR — version downgrade documented as unsupported; integer (not lexical) compare in the version gate; deleteClaim/PVC-retention and the one-per-namespace migration limitation documented in the README; docs/storage-immutability.md updated for KRaft. Tests — helm-unittest coverage for the broker pool name, the hook guards and the idempotent/fail-closed logic; a bats check for the helm-install-timeout budget ordering; and a Chainsaw ZK->KRaft migration suite that seeds a ZooKeeper cluster, drives the migration, and asserts the original broker PVC survives (not recreated) and the cluster reaches KRaft. The migration sequence and the Chainsaw assertions were validated against a live cluster. Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2.7 KiB
storageClass is immutable after creation
Every stateful Cozystack application that exposes a storageClass parameter declares it immutable in the chart schema (x-kubernetes-validations: [{rule: "self == oldSelf"}]). This document explains the contract and where it is enforced.
Why
storageClass binds the StatefulSet's PVC template. Kubernetes fixes PersistentVolumeClaim.spec.storageClassName at PVC creation time — editing volumeClaimTemplates[].spec.storageClassName on an existing StatefulSet does not retroactively migrate any data. A user who edits storageClass on an existing resource may believe their data is being moved to the new class; it is not. Locking the field at the schema layer makes that contract explicit.
What enforces it
| Consumer | Behavior |
|---|---|
Cozystack UI (packages/system/dashboard/images/console) |
Reads the CEL rule from the chart's openAPISchema and renders storageClass as a disabled, helper-text-annotated field on edit forms. Save-time overlay reinstates the original value before PUT. |
kubectl edit / kubectl patch against the cozystack aggregated apiserver |
Currently accepted — the apiserver does not yet evaluate CEL rules embedded in ApplicationDefinition.openAPISchema. The change passes through but does not propagate to existing PVCs (see "Why" above), so no data corruption is possible. Apiserver enforcement is tracked in cozystack/cozystack#2657. |
kubectl edit / kubectl patch against native CRDs |
Enforced today by the standard apiextensions apiserver. |
Apps covered
The following charts annotate at least one storageClass field as immutable:
clickhouse,foundationdb,harbor,http-cache,kafka(Kafka broker + KRaft controller),kubernetes,kubernetes-nodes,mariadb,mongodb,nats,openbao,opensearch,postgres,qdrant,rabbitmq,redis,vm-disk.
The kubernetes-nodes chart annotates both its per-pool storageClass (the worker node system-disk class, defaulted replicated) and its cluster field immutable. storageClass binds the same PVC-template contract as every other chart in the table, so the plain self == oldSelf rule applies for the reason in "Why" above. cluster is immutable for a different reason: it wires the pool's CAPI objects to one parent cluster kubernetes-<cluster>, and repointing a live pool at another cluster would orphan its running worker VMs rather than migrate them.
If you add a new stateful chart that exposes storageClass, annotate it the same way:
## @param {string} storageClass - StorageClass used to store the data.
## @immutable
storageClass: ""
For fields declared inside a @typedef, place ## @immutable on the line below the ## @field it should attach to.