Skip to content

Cinder (block storage) support: create, resize, snapshot with small/medium/large profiles#33

Merged
berendt merged 13 commits into
mainfrom
implement/issue-30-cinder-support
Jul 2, 2026
Merged

Cinder (block storage) support: create, resize, snapshot with small/medium/large profiles#33
berendt merged 13 commits into
mainfrom
implement/issue-30-cinder-support

Conversation

@berendt

@berendt berendt commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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:run metadata.

Change set (in commit order)

  1. 14bbff8 Move the Neutron profiles to scenarios/neutron/ — a hard, bisectable move ahead of a second service: the flat scenarios/{small,medium,large}.yaml become scenarios/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.
  2. 27ebe87 Add NewBlockStorageClient to internal/config — an authenticated BlockStorageV3 client mirroring NewNetworkClient line for line, reusing the same clouds.yaml resolution.
  3. 8247c2e Extract shared resource identity; add run-record service field — pure refactor plus additive fields. The Kind/Logical/Name/ID identity struct moves out of neutron into a shared internal/resource package (neutron.Kind/neutron.Resource become type aliases, so neutron files compile unchanged). run.Record gains Service (omitempty, read as neutron when absent so pre-Cinder records load unchanged) and VolumeType; neutron apply/chaos/monitor now stamp Service: "neutron" explicitly.
  4. 112e179 Add the Cinder plan modelinternal/cinder/plan, mirroring internal/plan: pure data, byte-identical JSON for the same input, a Plan of Volumes and Snapshots. ResizeToGiB is 0 when never resized, otherwise strictly greater than SizeGiB; snapshots reference their source volume by logical name. Validate resolves references and enforces size invariants; TotalGiB is shared by Summary and the quota pre-check.
  5. 45c6331 Add the Cinder scenario schema and deterministic generatorinternal/cinder/scenario with its own strict YAML schema (volume count and distributions for size, resize ratio, growth, snapshots-per-volume) under UnmarshalStrict. Generate expands scenario + seed into a byte-identical plan in a fixed draw order, guarding the resize decision behind a positive ratio (mirroring the neutron generator). A small.plan.json golden fixture locks byte-stability.
  6. f38a64d Add the Cinder client: volumes, snapshots, quota pre-checkinternal/cinder wrapping gophercloud v2 block-storage calls through the same timed metrics+OTEL seam as Neutron. Deterministic ostester-<id>-<logical> names and ostester:run/ostester:type metadata ride in the create request (no separate tag step). ExtendVolume adds the extend operation label; WaitForReady polls to available and fails on terminal error* status; WaitForGone backs cleanup ordering. Volumes are filtered server-side by metadata; snapshots (no metadata filter in gophercloud v2.12.0) are listed and filtered client-side. PrecheckQuota itemizes 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.
  7. 6697249 Add the Cinder staged executorinternal/cinder/executor with 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 partial Created. The retry/backoff policy is a deliberate local copy of internal/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.
  8. 4645560 Add metadata-based Cinder cleanupexecutor.Cleanup tears down in reverse dependency order (snapshots, then their source volumes, since Cinder rejects deleting a volume with live snapshots). Each kind is discovered by ostester:run metadata 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.
  9. 1b6e5f4 Add the cinder CLI namespace — the cinder command as a sibling of neutron (generate/apply/status/report/cleanup), wired into root and the global flags. apply takes optional --volume-type (unset = cloud default), recorded in the run record and fed to the per-type quota pre-check; --dry-run prints the plan summary before any client exists. Shared plumbing is factored where it helps: writePlanOutput, writeStatusTable over a small observer interface, resolveRun returning the whole record, and a requireService guard (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).
  10. 2bc7058 Ship the Cinder small/medium/large profilesscenarios/cinder/{small,medium,large}.yaml sized 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/large need raised quotas, guarded by the pre-check. Profile tests lock parse/name/validate/generate, the golden equality and default-quota fit for small, and large strictly larger than medium.
  11. 4326a75 Document 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 moved scenarios/neutron/ paths; §9 attribute value sets gain the new volume/snapshot kinds and extend operation plus a note that cinder apply --otel flows through the existing instruments unchanged. A new Cinder section (§15) documents commands, scenario schema and sizing, --volume-type semantics, the quota pre-check, and the snapshots-then-volumes cleanup; the layout tree grows internal/resource, internal/cinder, and the split scenarios tree; trailing sections renumbered.
  12. 4590b19 Put ctx first in writeStatusTable — small follow-up: context.Context first parameter for the shared status-table helper, matching the rest of the codebase.

Notes for the reviewer

  • Breaking path move (14bbff8): the old flat scenarios/{small,medium,large}.yaml paths no longer resolve. Anything pinning those paths (embedded-FS keys are now neutron/<name>.yaml) must be updated. This is intentional and called out in the commit.
  • Deliberate duplication: the executor's retry/backoff policy and the report builder are copied/instantiated per service rather than abstracted, because the shared internal/executor retry policy hardcodes the neutron classifiers. This trades a small amount of duplication for keeping each service's classifiers independent.
  • gophercloud limitation: snapshots have no server-side metadata filter in gophercloud v2.12.0, so snapshot discovery lists and filters client-side on the ostester:run metadata the tool itself wrote.
  • No divergence from the issue's intent was found in the commits — the small/medium/large profiles follow the issue's sizing table, and small is sized to fit default Cinder quotas.

Implemented by planwerk-agent 6b3f3ea with Claude:claude-opus-4-8

berendt added 13 commits July 2, 2026 20:38
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
berendt marked this pull request as ready for review July 2, 2026 20:16
@berendt
berendt merged commit 23e81ca into main Jul 2, 2026
2 checks passed
@berendt
berendt deleted the implement/issue-30-cinder-support branch July 2, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

First Cinder (block storage) support: create, resize, and snapshot volumes with small/medium/large profiles

1 participant