Cinder (block storage) support: create, resize, snapshot with small/medium/large profiles#33
Merged
Merged
Conversation
The scenarios/ tree grows a second service (Cinder) next, so the
Neutron profiles move into their own scenarios/neutron/ subdirectory
now, while the profile paths still have few external consumers. This is
a hard move with no fallback: the old flat scenarios/{small,medium,
large}.yaml paths stop working, the embedded-FS keys become
neutron/<name>.yaml, and every reference (embed directive, profile
tests, chaos test, Makefile, README) is updated in one bisectable
commit.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@b42labs.com>
The Cinder slice needs an authenticated BlockStorageV3 client. Add NewBlockStorageClient next to NewNetworkClient, mirroring it line for line with openstack.NewBlockStorageV3 and the same clouds.yaml resolution, so a cinder command reuses the existing auth plumbing. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Cinder resources have the same Kind/Logical/Name/ID identity shape as Neutron ones, so move that struct out of the neutron package into a small shared internal/resource package and keep neutron.Kind / neutron.Resource as type aliases — every neutron file, the executor, chaos, run, and their tests compile unchanged. Add two additive fields to run.Record: Service (neutron or cinder, omitempty and read as neutron when absent, so pre-Cinder records load unchanged) and VolumeType (cinder provenance). The neutron apply, chaos, and monitor records now stamp Service: "neutron" explicitly. This is a pure refactor plus additive fields: no behavior changes and the existing golden files stay green. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add internal/cinder/plan, mirroring internal/plan: pure data (slices only, byte-identical JSON for the same input) with a Plan of Volumes and Snapshots. A Volume's ResizeToGiB is 0 when never resized and otherwise its post-extend target, always strictly greater than SizeGiB; a Snapshot references its source volume by logical name. Validate resolves snapshot references and enforces the size invariants; Summary drives cinder apply --dry-run; TotalGiB (Σ final volume sizes + Σ snapshot sizes at the source's final size) is shared by Summary and the quota pre-check. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add internal/cinder/scenario with its own strict YAML schema (volumes count; volume_size_gib, volume_resized_ratio, resize_growth_gib, and snapshots_per_volume distributions) so a typo in either service's scenario keeps failing loudly under UnmarshalStrict. Validate enforces the size and growth floors that make every resize target strictly larger than its volume; Set covers every documented override key. Generate expands a scenario plus seed into a byte-identical plan, drawing per volume in a fixed order and guarding the resize decision behind a positive ratio (mirroring the neutron generator) so a ratio-0 plan stays stable. A golden small.plan.json locks byte-stability. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add internal/cinder wrapping the gophercloud v2 block-storage calls through the same timed metrics+OTEL seam as Neutron. Volumes and snapshots are created with the deterministic ostester-<id>-<logical> name and ostester:run / ostester:type metadata that rides in the create request itself (no separate tag step). ExtendVolume introduces the new "extend" operation label; WaitForReady polls to available and fails on a terminal error* status; WaitForGone backs cleanup's snapshot-before- volume ordering. Discovery filters volumes server-side by metadata; snapshots.ListOpts has no metadata field in gophercloud v2.12.0, so snapshots are listed and filtered client-side on the run metadata the tool itself wrote. FindVolumeType resolves --volume-type. PrecheckQuota reads the project's Cinder quotas and itemizes an oversized plan against the global and (when a type is set) per-type volume/snapshot/gigabytes quotas, with Cinder's 413 over-limit folded into the quota classification. The three telemetry instrument descriptions and the metrics package doc drop their Neutron-only wording, since Cinder now records through them. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add internal/cinder/executor driving the narrow Cinder create-and-wait seam through three strictly ordered stages: create every volume and poll to available, extend only the volumes with a resize target and re-poll, then snapshot the volumes. Snapshots are grouped by source volume — one work item per group, created strictly sequentially inside a group while groups run concurrently up to --concurrency — so a backend that rejects a snapshot of a still-snapshotting volume is handled without giving up cross-volume throughput. A volume that reaches a terminal error status fails its stage (and is still recorded for cleanup); a readiness deadline while the run is live is warned and tolerated. Quota errors fail fast and return the partial Created. The retry/backoff policy is a deliberate local copy of the internal/executor one, which hardcodes the neutron classifiers. Fake-backed tests cover stage ordering, extend-only-planned, per-volume snapshot serialization with cross-volume concurrency, quota fast-fail, a terminal volume error, transient retry, cancellation, and the concurrency bound; all pass under -race. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add executor.Cleanup, tearing a run's resources down in reverse dependency order: snapshots first, then the volumes they were taken from, since Cinder rejects deleting a volume whose snapshots still exist. Each kind is discovered by its ostester:run metadata and unioned (deduplicated by id) with the run record's created list, so cleanup never touches resources the tool did not create yet still reclaims one a metadata listing missed. Snapshot deletion is asynchronous, so each deleted snapshot is waited on until gone (bounded by --timeout) before the volumes are deleted. A 404 counts as success, so a second sweep is a no-op; the first non-404 error stops with the count so far. Fake-backed tests cover snapshots-before-volumes ordering, idempotency, the discovered/recorded union with dedup, 404 tolerance, and error propagation. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add the cinder command namespace as a sibling of neutron, wired into the root command and the existing global flags: generate, apply, status, report, and cleanup. report is the same service-agnostic builder the neutron namespace uses, instantiated a second time. apply takes an optional --volume-type resolved at apply time (unset means the cloud default), applied to every volume, recorded in the run record, and fed to the per-type quota pre-check; --dry-run prints the plan summary before any client exists. Factor the cheap shared plumbing out where it helps: generate's plan write becomes writePlanOutput, status's table loop becomes writeStatusTable over a small consumer-defined observer interface, resolveRun now returns the whole run record, and a requireService guard (treating an empty service as neutron) stops a service's commands from operating on another service's record. newRunID, the heartbeat, and the telemetry flush are reused as-is. Command tests cover subcommand registration, generate stdout/set/seed, the dry-run summary without a cloud, the cloud-required paths, and the cross-service guard in both directions (including a legacy record still read as neutron). Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Add scenarios/cinder/{small,medium,large}.yaml sized per the issue's
table and embed them alongside the neutron profiles. The small profile
(5 volumes, 1-5 GiB, 0-2 snapshots) is deliberately sized to fit
Cinder's common default quotas of 10 volumes / 10 snapshots / 1000 GiB,
so cinder apply runs against a fresh project without raising anything;
medium and large need raised quotas, guarded by the apply pre-check.
Profile tests lock that every profile parses, names itself, validates,
and generates; that small equals the golden fixture and fits the default
quotas; and that large stays strictly larger than medium.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Berendt <berendt@b42labs.com>
Update the status paragraph and the scope-by-phase table to note the first Cinder slice, point the §6 example and the built-in-profiles at the moved scenarios/neutron/ paths, and extend the §9 attribute value sets with the new kind (volume, snapshot) and operation (extend) values plus the note that cinder apply --otel flows through the existing instruments and dashboards unchanged. Add a Cinder section (§15) that documents the commands, the scenario schema and sizing table, --volume-type semantics, the quota pre-check, and the metadata-based snapshots-then-volumes cleanup; grow the layout tree with internal/ resource, internal/cinder, and the split scenarios tree; and add a roadmap entry. Renumber the trailing sections accordingly. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
Follow the Go convention of context.Context as the first parameter for the shared status-table helper, matching the rest of the codebase. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christian Berendt <berendt@b42labs.com>
testbed and testbed-monitor hardcoded the neutron namespace. A new SERVICE variable (default neutron) selects the CLI namespace, and the SCENARIO default follows it (scenarios/$(SERVICE)/small.yaml), so `make testbed SERVICE=cinder` exercises the Cinder slice. testbed-monitor picks the namespace up too, ready for cinder monitor (#31). Assisted-by: Claude:claude-fable-5 Signed-off-by: Christian Berendt <berendt@b42labs.com>
berendt
marked this pull request as ready for review
July 2, 2026 20:16
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.
Closes #30
First Cinder (block storage) slice for openstack-tester: generate, apply, status, report, and cleanup for volumes and snapshots across small/medium/large profiles, wired through the same auth, metrics/OTEL, run-record, and CLI plumbing that Neutron already uses. Volumes are created, optionally extended (resize), and snapshotted; cleanup tears them down in reverse dependency order by
ostester:runmetadata.Change set (in commit order)
14bbff8Move the Neutron profiles toscenarios/neutron/— a hard, bisectable move ahead of a second service: the flatscenarios/{small,medium,large}.yamlbecomescenarios/neutron/<name>.yaml, and every reference (embed directive, profile tests, chaos test, Makefile, README) is updated in one commit. No fallback for the old flat paths.27ebe87AddNewBlockStorageClienttointernal/config— an authenticatedBlockStorageV3client mirroringNewNetworkClientline for line, reusing the same clouds.yaml resolution.8247c2eExtract shared resource identity; add run-record service field — pure refactor plus additive fields. TheKind/Logical/Name/IDidentity struct moves out ofneutroninto a sharedinternal/resourcepackage (neutron.Kind/neutron.Resourcebecome type aliases, so neutron files compile unchanged).run.RecordgainsService(omitempty, read asneutronwhen absent so pre-Cinder records load unchanged) andVolumeType; neutron apply/chaos/monitor now stampService: "neutron"explicitly.112e179Add the Cinder plan model —internal/cinder/plan, mirroringinternal/plan: pure data, byte-identical JSON for the same input, aPlanofVolumesandSnapshots.ResizeToGiBis 0 when never resized, otherwise strictly greater thanSizeGiB; snapshots reference their source volume by logical name.Validateresolves references and enforces size invariants;TotalGiBis shared bySummaryand the quota pre-check.45c6331Add the Cinder scenario schema and deterministic generator —internal/cinder/scenariowith its own strict YAML schema (volume count and distributions for size, resize ratio, growth, snapshots-per-volume) underUnmarshalStrict.Generateexpands scenario + seed into a byte-identical plan in a fixed draw order, guarding the resize decision behind a positive ratio (mirroring the neutron generator). Asmall.plan.jsongolden fixture locks byte-stability.f38a64dAdd the Cinder client: volumes, snapshots, quota pre-check —internal/cinderwrapping gophercloud v2 block-storage calls through the same timed metrics+OTEL seam as Neutron. Deterministicostester-<id>-<logical>names andostester:run/ostester:typemetadata ride in the create request (no separate tag step).ExtendVolumeadds theextendoperation label;WaitForReadypolls toavailableand fails on terminalerror*status;WaitForGonebacks cleanup ordering. Volumes are filtered server-side by metadata; snapshots (no metadata filter in gophercloud v2.12.0) are listed and filtered client-side.PrecheckQuotaitemizes an oversized plan against global and per-type volume/snapshot/gigabytes quotas, folding Cinder's 413 into the quota classification. The three telemetry instrument descriptions and the metrics package doc drop their Neutron-only wording.6697249Add the Cinder staged executor —internal/cinder/executorwith three strictly ordered stages: create-and-poll every volume, extend only resize-targeted volumes and re-poll, then snapshot. Snapshots are grouped by source volume — sequential within a group, groups concurrent up to--concurrency— so a backend rejecting a snapshot of a still-snapshotting volume is tolerated without losing cross-volume throughput. Terminal volume errors fail the stage (still recorded for cleanup); a readiness deadline during a live run is warned and tolerated; quota errors fail fast and return the partialCreated. The retry/backoff policy is a deliberate local copy ofinternal/executor(which hardcodes the neutron classifiers). Fake-backed tests cover ordering, extend-only, snapshot serialization with cross-volume concurrency, quota fast-fail, terminal error, transient retry, cancellation, and the concurrency bound — all under-race.4645560Add metadata-based Cinder cleanup —executor.Cleanuptears down in reverse dependency order (snapshots, then their source volumes, since Cinder rejects deleting a volume with live snapshots). Each kind is discovered byostester:runmetadata and unioned (dedup by id) with the run record's created list. Snapshot deletion is asynchronous, so each is waited on until gone (bounded by--timeout) before volumes are deleted. A 404 counts as success (second sweep is a no-op); the first non-404 error stops with the count so far.1b6e5f4Add the cinder CLI namespace — thecindercommand as a sibling ofneutron(generate/apply/status/report/cleanup), wired into root and the global flags.applytakes optional--volume-type(unset = cloud default), recorded in the run record and fed to the per-type quota pre-check;--dry-runprints the plan summary before any client exists. Shared plumbing is factored where it helps:writePlanOutput,writeStatusTableover a small observer interface,resolveRunreturning the whole record, and arequireServiceguard (empty service treated as neutron) that stops a service's commands operating on another service's record. Command tests cover registration, generate stdout/set/seed, the cloudless dry-run, the cloud-required paths, and the cross-service guard both directions (including a legacy record read as neutron).2bc7058Ship the Cinder small/medium/large profiles —scenarios/cinder/{small,medium,large}.yamlsized per the issue's table and embedded alongside the neutron profiles.small(5 volumes, 1–5 GiB, 0–2 snapshots) is deliberately sized to fit Cinder's common default quotas (10 volumes / 10 snapshots / 1000 GiB) so it runs against a fresh project without raising anything;medium/largeneed raised quotas, guarded by the pre-check. Profile tests lock parse/name/validate/generate, the golden equality and default-quota fit forsmall, andlargestrictly larger thanmedium.4326a75Document the Cinder slice in the README — status paragraph and scope-by-phase table note the first Cinder slice; §6 example and built-in profiles point at the movedscenarios/neutron/paths; §9 attribute value sets gain the newvolume/snapshotkinds andextendoperation plus a note thatcinder apply --otelflows through the existing instruments unchanged. A new Cinder section (§15) documents commands, scenario schema and sizing,--volume-typesemantics, the quota pre-check, and the snapshots-then-volumes cleanup; the layout tree growsinternal/resource,internal/cinder, and the split scenarios tree; trailing sections renumbered.4590b19Put ctx first inwriteStatusTable— small follow-up:context.Contextfirst parameter for the shared status-table helper, matching the rest of the codebase.Notes for the reviewer
14bbff8): the old flatscenarios/{small,medium,large}.yamlpaths no longer resolve. Anything pinning those paths (embedded-FS keys are nowneutron/<name>.yaml) must be updated. This is intentional and called out in the commit.reportbuilder are copied/instantiated per service rather than abstracted, because the sharedinternal/executorretry policy hardcodes the neutron classifiers. This trades a small amount of duplication for keeping each service's classifiers independent.ostester:runmetadata the tool itself wrote.smallis sized to fit default Cinder quotas.Implemented by planwerk-agent 6b3f3ea with Claude:claude-opus-4-8