Repository navigation
refactor(api)!: move the API group to blockstor.org - #221
Draft
Andrei Kvapil (kvaps) wants to merge 67 commits into
Draft
Andrei Kvapil (kvaps) wants to merge 67 commits into
Andrei Kvapil (kvaps) wants to merge 67 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
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 |
Andrei Kvapil (kvaps)
force-pushed
the
refactor/api-group-blockstor-org
branch
3 times, most recently
from
October 24, 2026 22:38
4d7fdd0 to
bf67ca6
Compare
Andrei Kvapil (kvaps)
force-pushed
the
refactor/api-group-blockstor-org
branch
2 times, most recently
from
October 26, 2026 09:53
cfe04c2 to
3d93cb0
Compare
A clone of a source with volumes takes `clone-<target>` on the source and restores from it, and nothing ever deleted that snapshot. Deleting the clone left it behind, and the source could then never be deleted again through the API, which refuses a definition that has snapshots. Every CSI clone goes through this path. `rd d` of a clone now reaps that snapshot, on both the REST and the CLI door. Ownership is a prop the clone path stamps on the snapshot, never its name, so an operator's snapshot called `clone-<target>` is never touched. The snapshot is visible in `s l`, so another definition can have been restored from it, and that definition keeps reading it: new replicas are pinned to its nodes and restored from it by name. A dependent appears in two steps, the snapshot read and the definition create, so no list on the reap's side alone can see one between them. The reap marks the snapshot, then lists dependents past the cache, and only then deletes; a restore creates its definition, then reads the snapshot back past the cache and withdraws on a live mark, on a snapshot whose delete is in flight, or on one that is gone. Whichever of the list and the create comes second sees the other. A snapshot the reap keeps has its own mark taken off again, and a mark another reap wrote since is left alone. A withdrawal takes the definition the restore created back through the rollback, with its handshake: one a retry of the same restore adopted in the meantime is left to that retry. A withdrawal caused only by a reap's mark answers a retryable 409, since the reap may yet keep the snapshot; a snapshot that is gone, or whose delete was issued, answers 404 as before. The CLI checks the snapshot back the same way before it places replicas on a target it marked, a prepared one or its own leftover. The reap runs after the definition is deleted and before the delete is answered, detached from its caller on a 30s budget, and a mark left by a process that died expires. A kept or failed reap is logged and carried in the answer to the delete as a warning; the delete stands either way. A delete that was issued is read off the snapshot itself: the store now surfaces DELETE on a snapshot a finalizer still holds, the way it does for resources, and the snapshot list no longer stamps SUCCESSFUL beside it. A later `rd d` of the source names what is still there: a snapshot safe to delete, one being deleted that needs only a retry shortly, one kept because a named definition restores from it, and one that looks like a clone's but was never stamped. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A definition that leaves its layer stack empty and sits in a group that sets one was read two ways: the REST and witness gates resolved the group's stack, while the satellite built the volume from the empty one, which is the default stack. In a LUKS group that put plaintext on disk under a definition every gate called encrypted. The stack is now decided once, by the controller, and written onto the definition, as upstream decides it at resource creation. A definition with nothing on disk takes the group's stack. One that already holds data keeps the stack its storage was built with: giving it the group's now would have the satellite build a different stack over that data. It is annotated with the group's stack it did not get, and a warning event names it, so such a volume reads as what it is and can be found. The group is looked up under its folded name, since a definition keeps the group's name as the operator spelled it. Once set, the stack is never emptied again. Both stores keep a decided stack when an update carries none, as a caller writing back a copy read before the pin does, and the CRD refuses an update that empties it, so the controller never decides a second time over a volume that may by then hold data. A stack can still be replaced by another one. The spec.layerStack description says what an empty stack now means. The controller-side resolver that walked the group on read had no callers left and is removed. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The satellite read a definition's empty layer stack as the default and built the volume from it, even when the definition's group sets another stack. It now waits, on a short requeue, until the controller has written the group's stack onto the definition, and the intent builder refuses such a definition outright, so no path turns the empty stack into storage. The group is looked up under its folded name, so a definition naming it in another case waits too rather than being taken for one outside any group. A definition in no group, or in a group without a stack, keeps the default as before; a group that cannot be read applies nothing. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Format took "not a LUKS device" to mean "empty" and ran luksFormat. A volume that went to disk without LUKS and is later asked for it, by a layer stack that changed under it or a restore that brought data in, is not LUKS either, and luksFormat destroys it. Format now probes the device with `blkid -p` first and formats only one that carries no signature at all. DRBD internal metadata, a filesystem or a partition table refuses it with ErrDeviceHoldsData, naming what was found; a probe that cannot say the device is empty refuses it too. blkid exits 2 both for "nothing found" and for a device it could not open, so exit 2 counts as empty only without an "error:" line on stderr and for a path that stats as a block device; a missing path or a regular file is refused. The apply of that replica fails with the reason, and nothing is written. The satellite also records the refusal as a LUKSDeviceHoldsData warning Event on the replica, through events.k8s.io, for which its role gains the events rule. A signature left on reused extents is cleared with wipefs; the message says so. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The layer stack pin looked only at what replicas had reported. A definition can hold data before any of them has: linstor-migrate adopts volumes that are already full and latches Initialized on them, and the latch is set once the controller has seen a real write. Either way the storage was built under the stack the definition carried, so the pin keeps it. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The converter copied the resource definition's layer_stack column as is. LINSTOR resolves the stack per resource and often leaves that column empty, so the stack those volumes were built with lives only in LAYER_RESOURCE_IDS. A converted definition with an empty stack then took whatever blockstor resolved for it, not what is on disk: a LUKS volume lost its LUKS layer, and a definition in a group with another stack was read through that group's. An empty stack is now read off the replicas' layer chains. Replicas built in different ways leave it unset and are reported, as is a replica built otherwise than the stack its definition names, since the satellite would not bring it up as it is. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The page said an RD with an empty stack inherits its group's at read time, which is what put plaintext on disk under encrypted definitions. It now says the stack is fixed once on the definition, by the REST create path or by the controller's pin, what happens to a definition that already holds data, what the operator sees, and how to find such definitions. The migration guide and the REST create path's comment say the same. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A clone or restore wrote the source's stack onto the new definition as stored, reading an empty one as the default. Under a group that sets a stack, an empty one is not decided yet: the controller pins it to the group's stack or to the default depending on whether data is already there, and the satellite builds nothing until it does. The copy took the default regardless and kept it for good, so a shell of an empty definition in a LUKS group stayed plaintext with no data forcing it. The stack is now resolved through the group. An undecided source, leftover or prepared target is answered 503 (the CLI exits with an error) before anything is written, and a group that cannot be read is an error rather than the default. A volume-less clone copies no bytes, so it takes only a stack the source chose and is otherwise left for its own group to decide. A copy short of what its group asks for carries the unapplied-group-layer-stack annotation the controller puts on a definition pinned over data. The restore marker goes onto a prepared target only if, in the patch that writes it, the target's stack still has the source's layers, so a stack rewritten after the target was judged is refused rather than restored into. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The pin compared the stack it applied with the group's in order and case, while clone and restore compared them as sets. A group spelling its stack in another order or case flagged every definition holding data as short of it. Both now share one comparison. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The recorder writes events.k8s.io events, which the stand's controller role did not grant, so the layer-stack warning was dropped. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The unapplied-group-layer-stack annotation was set on any difference from the group's stack, so an encrypted copy in a plaintext group was flagged as if it were the plaintext volume the annotation exists to find. It is now set only when a layer the group asks for is missing. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
LINSTOR builds a diskless replica as DRBD over STORAGE whatever the definition's stack, so counting it made the replicas of every LUKS definition with a tiebreaker look divergent and left its stack unset. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…group Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A thick LV is allocated with -Wn -Zn, without zeroing, and nothing wiped a volume's signatures when it was deleted, so a new LV starts with whatever its extents held. When that was a deleted LUKS volume's header, the LUKS layer found a header, skipped the format and opened it with the cluster passphrase every volume shares: the new volume showed the old one's contents. Any other leftover, a filesystem or DRBD metadata, made the LUKS layer refuse to format a volume that holds nothing. CreateVolume now creates the LV under a temporary name, the volume's name with a .bsnew suffix, runs wipefs over it and only then renames it to the volume's name, so nothing under a volume's name carries a signature another volume left. wipefs erases the signatures libblkid recognises, the LUKS header magic among them, and nothing else: the rest of the old volume's blocks stays on the extents, and a plaintext volume can read it back from the raw device. Zeroing the LV would close that, at the cost of writing a large thick LV in full before it can be used. An LV already under the volume's name is the volume and is never wiped. A temporary LV left by a run that stopped before the rename is wiped and renamed rather than created again. A failed listing, wipe or rename fails CreateVolume, and a failed wipe or rename leaves nothing under the volume's name. The orphan sweeper only knows LVs named like a volume, so it never sees a temporary one; DeleteVolume removes a leftover one along with the volume. DeleteVolume wipes nothing: lvremove refuses an LV that is still open, and that LV has to keep its data. The next CreateVolume over the freed extents wipes their signatures anyway. Thin LVM, ZFS and the file provider do not need this: a fresh thin LV, zvol or backing file reads zeroes where nothing has been written. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
… name A definition keeps its group's name as the operator spelled it (`--resource-group DfltRscGrp`), while the group's CR is stored under the lowercased name. The witness gate and the effective-props resolver (which the satellite, the dispatcher and the controller render DRBD options with) looked the group up by the spelled name, missed it, and went on as if there were no group: a STORAGE-only group got a DRBD witness, and the group's DRBD options were left out of the rendered config. Both now look the group up under the folded name, as the store does. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
An L6 cell and an L7 replay drive the layer-stack pin through the linstor CLI: a definition with nothing on disk, created in a group without a stack, has none, and takes the LUKS group's stack once rd modify moves it there; the REST create copies a group's stack itself, so only the move shows the controller's pin. One with data keeps DRBD,STORAGE when its group gains LUKS, is annotated with the group's stack, and gets the LayerStackNotInherited Event; a clone taken straight after the group change is refused as undecided until the pin and then succeeds with the source's stack. The cell also checks the data reads back unchanged. The replay runner gains two await kinds to read what the CLI does not show: rd_layer_stack (spec.layerStack) and rd_annotation. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
… to its group The mark recorded the group's stack at pin time and was never looked at again, so a definition whose group later dropped the layer it lacks still read as short of its group. Each reconcile now re-checks a flagged definition: the mark follows the group's current stack, and goes once the group no longer asks for a missing layer or is deleted. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…refusal The hint suggested wipefs for any signature, including DRBD metadata on a volume that is already in use, where wiping destroys the volume. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The refresh looked only at a definition that already carried the mark, and could now remove it, so a group that dropped a layer and took it back left the definition unflagged for good. Every definition with a decided stack is now checked against its group's current stack: flagged, with a warning Event, while it lacks a layer of it, and cleared once it does not. That also flags one whose group gains a layer after the pin. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…g it The next create renamed the leftover whatever its size, so a volume grown in between came up at the size the crashed call asked for. The leftover was never exposed under the volume's name, so it is removed and the LV is created again at the volume's current size. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…al case rd create, rg create and rg modify validated a layer stack case-insensitively and stored it as the caller spelled it, and so did the CLI. Some readers compare the stored value exactly: the satellite controller's DRBD check read `drbd,storage` as a stack without DRBD, so allocation waited and requeued while the apply path brought DRBD up. Every write path now stores the canonical spelling, and the satellite check folds case for objects written before this or applied by hand. Fixes #204 Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…source Since the mark also flags a stack set by hand that lacks a layer of the group, a volume-less clone dropped such a stack, LUKS,STORAGE in a DRBD,STORAGE group say, and the controller pinned the clone to the group's plain stack. A stack pinned over data is always the default one, so only a flagged source with the default stack hands the clone nothing now. The annotation's comment and the docs describe its wider meaning. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…al LUKS step The converter copied every property, so a passphrase LINSTOR kept in one landed in plaintext in YAML that is applied past the REST guard. The props index now leaves those keys out. The LUKS warning sent operators to spec.encryption, which nothing reads; it now says to add the cluster passphrase to each volume as a key slot. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A restore or clone into a name with no definition but with a diskless replica or a witness under it created the definition over that replica. The stamp on its node then failed, and the rollback of the definition cascaded over every replica under the name, deleting one the request had not made. The materialisation now refuses such a name with 409 before it writes anything, and names the replicas. A replica holding data is still taken as placed, as before, and one already being deleted is no obstacle. Fixes #203 Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…-initialising it A replica that goes down while Primary and writing leaves its activity log open, and drbdmeta answers the metadata probe with "please apply-al first". The satellite read that as "no metadata" and ran create-md --force, which destroys the replica's generation identifiers and dirty bitmap; if it held the only up-to-date copy, that copy came back as something to sync into. It treated a crash the same as a foreign superblock, because it had no per-volume record to tell them apart. The record is the set of GIs this replica's volume was seen to carry. The observer writes every GI it reads into a per-resource history file in the satellite's state directory, which outlives the status field (that one is dropped whenever the volume leaves a data-bearing state) and a restart; the GI in the status counts too. A dirty log on a volume with no record is initialised as before: a clone, a receive, a volume added to a live resource. One with a record is repaired with apply-al, and some UUID then in its metadata, current, a peer slot's bitmap UUID or history, has to be one it was seen with, compared without the Primary bit; otherwise the superblock is not this replica's and it gets metadata of its own. A Primary that lost a peer keeps its old current UUID only in that peer's bitmap slot, which get-gi never shows, so the check reads dump-md. A failed apply-al or metadata read is an error rather than a re-initialisation. Fixes #184 Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The L6 cell and the L7 replay now assert both refusals, and the cell checks that the passphrase still opens the volume afterwards. The vd door is named modify-passphrase, as python-linstor spells it. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A clone or restore rollback marks the definition in-progress while it runs, and every retry over that mark answers 409. An apiserver replica OOM-killed or lost between writing the mark and taking it off left it for good, and linstor-csi retried CreateVolume against the 409 until an operator cleared the prop by hand. The mark now carries the time it was written. A retry, on the REST doors and in the CLI, takes over a mark older than two minutes, well past the rollback's own budget, and resumes the leftover. The mark only ever stands over a definition left whole, and a rollback still alive past its budget finds the mark gone at its next step and stands down without deleting. Fixes #213 Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
… record whole drbdmeta prints a v09 volume's history UUIDs as a block over several lines, and v08's as one uuid block. The parser read neither, so a GI that a resync had moved into the history was never found and the replica was initialised after all. It now reads both blocks and only bare UUID statements, so the bitmap and the device UUID stay out. External metadata takes the same check as internal: dump-md finds the meta disk from the .res. Deleting a resource now drops its cached last GIs together with the file, under the lock the record is written under, so a replica created again records its first GI. The record is fsynced before it replaces the old one, since it exists for a node that went down hard. The observer is built by a constructor a test can drive, which pins the wiring into the reconciler. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…finition A volume formatted with a property's key fails to open with the Secret's value, and nothing named the property. The satellite now logs the keys it dropped, never their values. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…fixes - The orphan refusal moves to the store and runs on the CLI restore door too, which created the definition over the orphan the same way. It refuses diskless replicas and leaves the controller's tie-breaker witness to the stamp, which promotes it. - A CLI rollback now runs on a one-minute budget of its own. The stale takeover rests on every rollback being over long before two minutes, and the CLI's had no deadline at all. - A retry that finds the in-progress mark already taken off by another retry proceeds instead of answering that a rollback is running. - Tests for the store-backed prune, which production runs, and for the CLI takeover. - The snapshot-delete refusal sits above handleSnapshotDelete's doc comment rather than under it, and the unused RestoreSourceInUse is gone. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The satellite served GET /v1/satellite/snapshots/... on every node address with no authentication, and the answer is the raw send stream of a snapshot. Any pod that could reach a node IP could read any volume with a snapshot on that node; in Cozystack, platform pods without an egress policy could. The stream now runs over HTTPS with mutual TLS. Every satellite presents a certificate for the fixed name blockstor-satellite, signed by a stream CA, and verifies the peer's against it in both directions. The files are read on every handshake, so a rotated certificate is picked up without a restart. A satellite started without the three --stream-tls-* flags serves and fetches nothing, and cross-node clone and restore fall back to a DRBD initial sync; a partial set refuses to start. The stand issues the certificate from a CA of its own rather than blockstor-api-ca, since a stream certificate is also a client certificate and the apiserver accepts any from its CA. Fixes #208 Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
…ap fixture Remove the temporary file when the rename fails, state the window the delete lock leaves open, and give the dump fixtures the bitmap header drbdmeta prints for v09. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The mark was the clone and the time to the second, so two reaps of one clone started in the same second wrote the same mark, and a reap that kept the snapshot took off the other's. The CLI can run two such reaps. A restore then found no reap under way while the other one went on to delete its source. The mark now carries a random token per reap. The owner check reads the snapshot past the cache too, so a clone deleted right after it was created does not skip the reap. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Copied onto a snapshot an operator takes, the owner mark would make that snapshot claim the clone path took it. Both marks are dropped from the props a snapshot takes and refused on definition edits. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
… base # Conflicts: # docs/cli-parity-known-deltas.md # internal/cli/snapshot.go
… base # Conflicts: # cmd/controller/main.go
…ame base # Conflicts: # pkg/rest/rd_clone.go
…ame base # Conflicts: # docs/linstor-migration.md # pkg/linstormigrate/convert.go # pkg/rest/vd_passphrase_bug_233.go
The CRDs lived under blockstor.cozystack.io. The project is meant to stand on its own, under the blockstor.org domain it publishes its site on, and the group is the one name that cannot change cheaply once clusters carry objects under it. Nothing is deployed on it yet, so it moves now, with no migration: clusters running a build from before this have to be reinstalled. Every kind moves to blockstor.org/v1alpha1: the CRDs (regenerated, one file per kind under the new name), the RBAC markers and roles, the finalizers, the resize-pending annotation, the scrubber that keeps the group off the LINSTOR wire, the samples, the stand manifests, the e2e and cli-matrix scripts and the docs. The LINSTOR migrator stamps apiVersion from the scheme, so the objects it writes come out under the new group; its golden manifests say so. The controller's leader-election lease moves with it. Two strings keep the old spelling on purpose: the seed of the stable node UUIDs, which is a hash input rather than the group and must not change, and the history in CHANGELOG.md and dated audit notes. BREAKING CHANGE: the API group is blockstor.org; objects under blockstor.cozystack.io are not read. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The labels and annotations blockstor writes lived under blockstor.io/ and topology.blockstor.io/, a domain the project does not hold. They move to blockstor.org/ and topology.blockstor.org/ with the API group, for the same reason and at the same moment: nothing is deployed on them yet. Every key moves, the original-name annotation, the label index of replicas and pools, the controller's bookkeeping annotations, the unapplied-group-layer-stack annotation, the topology labels synced from Kubernetes nodes, and the wire scrubber that keeps them off REST reads. The CRD descriptions are regenerated, and the scripts and docs follow. BREAKING CHANGE: labels and annotations under blockstor.io/ are not read. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Andrei Kvapil (kvaps)
force-pushed
the
refactor/api-group-blockstor-org
branch
from
October 26, 2026 14:04
3d93cb0 to
3125e54
Compare
This was referenced Oct 27, 2026
This branch has not been deployed
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.
Moves the API group from
blockstor.cozystack.iotoblockstor.org, and the label and annotation keys fromblockstor.io/toblockstor.org/. The project is meant to stand on its own, under the domain it publishes its site on, and the group is the one name that cannot change cheaply once clusters carry objects under it. Nothing runs on it yet, so it moves now, with no migration.Merge this last. It is stacked on #190, #200, #206, #215, #216, #217, #218, #219 and #220: the branch merges all of them and then renames, so the conflicts between them are resolved here once. After they land I'll rebase it onto main and redo the rename on the result, which is mechanical.
What moves:
blockstor.org/v1alpha1: the CRDs (regenerated, one file per kind under the new name), the RBAC markers and roles, the samples and the stand manifests;topology.blockstor.org/labels synced from Kubernetes nodes.The LINSTOR migrator stamps
apiVersionfrom the scheme, so the objects it writes come out under the new group; its golden manifests say so.Two strings keep the old spelling on purpose: the seed of the stable node UUIDs, which is a hash input, not the group, and must not change, and the history in
CHANGELOG.mdand dated audit notes.cozystack/cozystack#2942 follows the group in its chart and tests.
Breaking change
The API group is
blockstor.org, and so are the label and annotation keys. A cluster running a build from before this has to be reinstalled: objects underblockstor.cozystack.ioand keys underblockstor.io/are not read.Testing
go test ./...with envtest assets, including the migrator's golden manifests: green apart from the knownpkg/restshutdown flake.