Skip to content

Volume actions

Terms used below: the pool is the single storage pool every volume, and every instance root disk, is carved from. The record is the layer’s own map from a vol-, snap- or i- identifier to the underlying resource.

Assumption: RunInstances §9.4 Option A — the vol- ↔ the block-storage service’s own UUID binding lives in a store owned by the layer, because the block-storage service’s own metadata on a volume is customer-writable and therefore cannot hold an identifier the layer trusts. Under Option B (the block-storage service volume-image metadata property) every id resolution below becomes GET /volumes/detail?metadata={"vol-id":…}, one call per id, and a customer who edits that metadata orphans their own volume.

Nothing on this page is enforced that the catalog does not enforce. Where a figure is advertised but not yet limited on the hypervisor, it is marked NOT ENFORCED and named in section 9. That is the opposite of how the rest of this platform works, and it is a defect, not a design.

Implemented with differences.

#AWSThis layerWhyWhat breaks for a client assuming AWS
D1Volume types gp2, gp3, io1, io2, st1, sc1, standard.One type. standard is accepted and returned; every other name is rejected with InvalidParameterValue.The hardware has one class of disk — 7200 rpm SATA. standard is Amazon’s own name for magnetic storage, so the alias is honest; gp3 and io2 would be a promise of SSD latency this hardware cannot make.Terraform aws_ebs_volume with type = "gp3" errors instead of silently getting a slower disk. This is deliberate — see section 9.1.
D2Iops and Throughput are settable on CreateVolume and ModifyVolume for the provisioned types.Both parameters are rejected with InvalidParameterValue. The performance of a volume is fixed by its type and published in Volume types.There is one type and it is not provisionable. Accepting the parameter and ignoring it would let a customer believe they had bought IOPS.A module that always sets iops must set it conditionally on type, which is what the AWS provider already requires.
D3Size 1 GiB to 16 TiB depending on type.1 GiB to 1000 GiB (volume_types[0].min_gib, max_gib). Outside that: InvalidParameterValue.The whole pool is 1400 GiB and it also holds every instance root disk.A request for a 2 TiB volume errors rather than being silently truncated.
D4Multi-attach on io1/io2.A volume attaches to one instance at a time. MultiAttachEnabled is rejected.Not implemented, and not safe on a single-node LVM backend without a cluster filesystem.aws_ebs_volume.multi_attach_enabled errors.
D5Encrypted, KmsKeyId.Rejected with InvalidParameterValue. There is no key management service.No KMS exists on this platform. Returning encrypted: false for a request that asked for true would be a lie a compliance audit would later find.A module that sets encrypted = true by policy fails loudly at plan time rather than shipping unencrypted storage.
D6DescribeVolumes MaxResults 5–500.5–500, default 1000 when absent, with a nextToken when more exist.Conventions, “Pagination”.None.
D7A deleted volume “might appear” briefly.DeleteVolume returns after the block-storage service accepts the delete; the volume then reports deleting until the block-storage service finishes, and afterwards InvalidVolume.NotFound. There is no synthesised deleted window.the block-storage service’s delete is asynchronous and the object disappears at the end of it; unlike a terminated instance, no client polls a volume to a terminal state — the AWS provider’s aws_ebs_volume delete waits for not-found.None.
D8Another account’s volume id behaves as not-found.InvalidVolume.NotFound, byte-identical to an id that never existed.Conventions, “Accounts and tenancy”, Visibility.None.
D9Volume IOPS and throughput are enforced per volume.NOT ENFORCED. The catalog declares 48 IOPS and 11 MB/s per volume; no a block-storage QoS specification or front-end throttle applies it today. A single busy volume can take the whole pool’s throughput.bootstrap/roles/openstack/tasks/resources.yml creates the compute service flavors from the catalog and nothing else; there is no volume_type/qos_spec task.A noisy neighbour. This breaks the platform’s own first rule — every capability figure is enforced, not advertised — and it blocks the first customer. Section 9.2.

Volume actions are EC2 actions. They go to the EC2 endpoint, not to the block-storage service:

https://ec2.shelfcs.com

Signed with SigV4 using an access key pair, verified by the identity service’s the identity service’s signature-verification call. See Service endpoints for the full host list and Authentication for the signing rules.

The same volumes are reachable through the block-storage service’s own API at https://volume.shelfcs.com — see Storage developer guide. The two views are of one object; a volume created through EC2 is visible, resizable and deletable through the block-storage service and vice versa. Only the identifier differs (vol-… against a UUID).

ActionResource scope
ec2:CreateVolumevolume/*
ec2:DescribeVolumesRequires "Resource": "*"
ec2:DescribeVolumeStatusRequires "Resource": "*"
ec2:AttachVolumeBoth volume/<id> and instance/<id>
ec2:DetachVolumeBoth volume/<id> and instance/<id>
ec2:ModifyVolumevolume/<id>
ec2:DeleteVolumevolume/<id>

AttachVolume and DetachVolume evaluate against both resources. A policy naming only the volume denies the call. This is Amazon’s rule and it is kept, because a policy written for AWS must not become more permissive here.

Every action accepts DryRun. Permitted: DryRunOperation. Denied: UnauthorizedOperation (HTTP 403).

ParameterModel memberTypeRequiredConstraintsStatus
AvailabilityZoneAvailabilityZonestringYeshel1-a. No default. Unknown zone: InvalidParameterValue.supported
SizeSizeintegerConditionalGiB, 1–1000. Required unless SnapshotId is given.supported
VolumeTypeVolumeTypestringNostandard only. Defaults to standard.supported
SnapshotIdSnapshotIdstringNosnap-…. Restores from a snapshot.supported
TagSpecification.NTagSpecificationslistNoResourceType must be volume.supported
DryRunDryRunbooleanNosupported
Iops, ThroughputintegerNorejected with InvalidParameterValue (D2)
Encrypted, KmsKeyIdNorejected with InvalidParameterValue (D5)
MultiAttachEnabledbooleanNorejected with InvalidParameterValue (D4)
OutpostArnstringNorejected with InvalidParameterValue

AvailabilityZone is required and has no default. A volume attaches only to an instance in its own zone, and there is no cross-zone attach. There is one zone today; ask DescribeAvailabilityZones for it rather than writing hel1-a into your code, so that the day there are two you do not have to.

With SnapshotId, Size may be omitted to take the snapshot’s size, or given to create a larger volume. It may never be smaller than the snapshot.

POST /v3/{project_id}/volumes, body volume:

EC2the block-storage service
Sizesize
AvailabilityZoneavailability_zone
VolumeType (standard)volume_type hdd
SnapshotId (resolved)snapshot_id
TagSpecification.Nmetadata, key-prefixed so customer metadata and tags cannot collide

The vol- identifier is minted by the layer and written to the record against the UUID the block-storage service returns, before the response is sent. A crash between the block-storage service’s 201 and that write leaves an orphaned the block-storage service volume that the EC2 API cannot see; it is still billed. This is the same failure the missing ClientToken causes below, and it is reconciled by the same sweep — section 9.3.

volumeId, size, volumeType (standard), status (creating), availabilityZone, createTime, snapshotId where applicable, tagSet, encrypted (false), multiAttachEnabled (false).

volumeType is returned as standard, which is both the value accepted on input and the value a client’s model expects. Our own name for it, hdd, is never on the wire.

CodeStatusCause
InvalidParameterValue400Size out of 1–1000, unknown type, unknown zone, or a rejected parameter (D1, D2, D3, D4, D5)
InvalidParameterCombination400Neither Size nor SnapshotId; or Size smaller than the snapshot
InvalidSnapshot.NotFound400No such snapshot, or it belongs to another account
VolumeLimitExceeded400The project’s the block-storage service quota would be exceeded
InsufficientVolumeCapacity500The pool cannot fit it (section 6)

The Amazon EC2 model declares no ClientToken for CreateVolume, so none can be sent — an SDK rejects the parameter before the request leaves the machine. A retry after a lost response creates a second volume, and bills for it.

Tag volumes at creation and reconcile by tag if you retry automatically. Terraform does this for you: aws_ebs_volume records the id in state before it retries anything.

ParameterTypeConstraints
VolumeId.Nlist^vol-([0-9a-f]{8}|[0-9a-f]{17})$. Anything else: InvalidVolumeID.Malformed
Filter.NlistSee below
MaxResultsinteger5–500. Cannot be combined with VolumeId.N
NextTokenstringOpaque. Cannot be combined with VolumeId.N
DryRunboolean

Paginated. An empty page may still carry a NextToken — filters are applied after the page is assembled (conventions, “Pagination”, Filters), so a page can filter down to nothing and still not be the last.

Supported — a filter is supported when the element it matches is emitted:

FilterMatched against
statuscreating, available, in-use, deleting, error
attachment.instance-idThe instance a volume is attached to
attachment.deviceThe requested device name
attachment.statusattaching, attached, detaching
attachment.delete-on-termination
availability-zone
sizeGiB, exact
snapshot-id
volume-typeAlways standard
create-timeWildcards * and ? allowed
volume-id
tag:<key>, tag-key

Rejected with InvalidParameterValue: encrypted, multi-attach-enabled, fast-restored, throughput, iops, outpost-arn. They describe elements this layer never emits, and matching nothing silently is the failure the pagination convention forbids.

For each volume: volumeId, size, snapshotId, availabilityZone, status, createTime, attachmentSet, volumeType, encrypted (false), multiAttachEnabled (false), tagSet.

Each attachmentSet item: volumeId, instanceId, device, status, attachTime, deleteOnTermination.

Computed from the block-storage service status:

the block-storage serviceEC2
creating, downloadingcreating
availableavailable
attaching, in-use, detachingin-use
deletingdeleting
error, error_deleting, error_extending, error_restoringerror
reservedavailable — the block-storage service reserves a volume between the attach request and the attach; EC2 has no such state
maintenance, backing-up, restoring-backup, retypingNOT MAPPED — section 9.4

6. Capacity, and what “thin” means here

Section titled “6. Capacity, and what “thin” means here”

Every volume and every instance root disk comes out of one pool:

FigureValueSource
Pooldefault
Sellable1400 GiB
Reserved for instance root disks1200 GiB thin LV nova-instances
Volume GiB already soldTracked internally; free space plus that figure must still cover the catalog

Root volumes are thin-provisioned, but the sum of what has been sold is capped, so the pool cannot fill silently behind a customer who is within their quota. A CreateVolume that would take the pool past that cap fails with InsufficientVolumeCapacity rather than succeeding into an over-committed pool.

[!warning]

One box, one pool, one disk class. There is no replication of a volume across hosts, and as of today the pool is not mirrored across disks. See our internal readiness list gap 1. Treat a volume as durable against a process crash, not against a disk failure, and take snapshots.

ParameterTypeRequiredNotes
VolumeIdstringYesMust be available
InstanceIdstringYesMust be in the same zone, and running or stopped
DevicestringYesThe device name inside the guest
DryRunbooleanNo

Mapped to the compute service POST /servers/{server_id}/os-volume_attachments with volumeId and device — the compute service, not the block-storage service, because the compute service owns the hypervisor side of the attach and calling the block-storage service’s os-attach directly would leave the compute service’s own view of the instance wrong.

The device name is a request, not a guarantee: the guest kernel decides what the device is actually called, and /dev/sdf may arrive as /dev/vdb. Identify volumes inside the instance by filesystem UUID or label — blkid, then UUID=… in /etc/fstab — or a reboot that renumbers devices mounts the wrong one.

Attaching does not partition, format or mount anything. That happens inside the instance, and we do not do it for you.

Errors

CodeStatusCause
VolumeInUse400Already attached to an instance
InvalidVolume.ZoneMismatch400Volume and instance are in different zones
IncorrectState400The volume is not available, or the instance is not in a state that can take an attach
InvalidParameterValue400The device name is already in use on that instance
InvalidInstanceID.NotFound400No such instance, or another account’s
AttachmentLimitExceeded400Per-instance attachment limit reached

A volume attaches to one instance at a time. There is no multi-attach (D4).

deleteOnTermination is false for every volume attached by this action. A volume attached after launch survives the instance. Only a root volume created by RunInstances from a block device mapping carries deleteOnTermination: true.

ParameterTypeRequiredNotes
VolumeIdstringYes
InstanceIdstringNoVerified if given; mismatch is InvalidParameterValue
DevicestringNoVerified if given
ForcebooleanNoDetach without the guest releasing it
DryRunbooleanNo

Mapped to the compute service DELETE /servers/{server_id}/os-volume_attachments/{volume_id}. With Force, to the block-storage service os-force_detach, which drops the attachment record whether or not the hypervisor agreed.

[!warning]

Unmount the filesystem inside the instance before detaching. Detaching a mounted, written filesystem is the equivalent of pulling the disk out, and loses whatever was in flight.

Force detaches regardless. It is for an unresponsive instance, and it risks filesystem corruption. It does not stop the instance first.

Detaching does not stop billing. The volume still exists and is still charged until it is deleted (section 10).

ParameterTypeRequiredNotes
VolumeIdstringYes
SizeintegerYesLarger than the current size, at most 1000
DryRunbooleanNo
VolumeType, Iops, Throughput, MultiAttachEnabledNorejected with InvalidParameterValue

Mapped to the block-storage service POST /volumes/{volume_id}/action, os-extend.

Volumes grow and never shrink. A Size at or below the current size is InvalidParameterValue, not a no-op — a silent no-op would let a shrink attempt look like it worked.

Response: volumeModification with volumeId, modificationState (modifying, then optimizing, then completed), originalSize, targetSize, startTime. DescribeVolumesModifications is not implemented; poll DescribeVolumes for the new size instead. A client that waits on DescribeVolumesModifications receives InvalidAction — section 12.

Growing the volume does not grow the filesystem on it. Inside the instance, extend the partition and then the filesystem — growpart /dev/vdb 1, then resize2fs or xfs_growfs. Until you do, the extra space is invisible to the guest and you are paying for it.

Extending an attached volume is supported by the block-storage service for a volume in in-use, but the guest only sees the new size after a rescan (echo 1 > /sys/class/block/vdb/device/rescan) or a reboot.

ParameterTypeRequired
VolumeIdstringYes
DryRunbooleanNo

The volume must be available, not attached. Deleting an attached volume is VolumeInUse.

[!warning]

Deletion destroys the data. We hold no copy, and there is no recycle bin. Snapshots taken before deletion survive and are billed separately.

Deleting an already-deleted volume returns InvalidVolume.NotFound. Callers retrying a delete treat that as success — the AWS provider does.

Billing: the charge ends when the volume is destroyed, not when it is detached. See How prices are set for what a volume-GiB-hour currently costs, which is a number that does not yet exist — attached-storage rating is not implemented, so volumes are today unbilled. That is a revenue defect, not a customer discount, and it will change.

ParameterTypeNotes
VolumeId.Nlist
Filter.Nlistvolume-status.status, availability-zone, volume-status.details-name, volume-status.details-status
MaxResultsinteger5–1000
NextTokenstring

Paginated.

Response: volumeId, availabilityZone, volumeStatus.status (ok, impaired, insufficient-data), volumeStatus.details, actionsSet (always empty), eventsSet (always empty).

volumeStatus.status is derived from the block-storage service status alone: error* states map to impaired, everything else to ok. There is no per-volume health check behind it, so ok means “the block-storage service has not recorded an error”, not “we have verified this disk is healthy”. SMART monitoring runs at the host level (smartd), not per volume, and does not feed this field.

eventsSet is always empty: there are no scheduled-maintenance events, because there is no maintenance scheduling system. A client that watches this field for retirement notices will never see one — that is a real gap, not a promise that nothing will ever fail.

Named so that their absence is deliberate. Each returns InvalidAction:

DescribeVolumesModifications, EnableVolumeIO, ModifyVolumeAttribute, DescribeVolumeAttribute, CreateVolumePermission and the whole *VolumeAttribute family, AttachVolume with MultiAttachEnabled, fast snapshot restore, EBS direct APIs (ListChangedBlocks, GetSnapshotBlock), Elastic Volumes’ online type change, and every provisioned-performance action.

aws --endpoint-url https://ec2.shelfcs.com --region hel1 \
ec2 create-volume --availability-zone hel1-a --size 20 --volume-type standard \
--tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=data}]'
aws --endpoint-url https://ec2.shelfcs.com --region hel1 \
ec2 attach-volume --volume-id vol-… --instance-id i-… --device /dev/sdf
aws --endpoint-url https://ec2.shelfcs.com --region hel1 \
ec2 describe-volumes --filters Name=attachment.instance-id,Values=i-…

--region hel1 must match the region in the credential scope, or every call returns SignatureDoesNotMatch with no diagnosable cause.

resource "aws_ebs_volume" "data" {
availability_zone = "hel1-a"
size = 20
type = "standard"
tags = { Name = "data" }
}
resource "aws_volume_attachment" "data" {
device_name = "/dev/sdf"
volume_id = aws_ebs_volume.data.id
instance_id = aws_instance.app.id
}

type = "standard" is required: the provider’s default is gp2, which this layer rejects (D1). Omitting type therefore fails at apply, not at plan.

aws_volume_attachment sets force_detach = false by default; leave it there. skip_destroy = true is the safe setting when the volume outlives the instance.

lsblk
mkfs.ext4 /dev/vdb
blkid /dev/vdb # take the UUID
echo 'UUID=<uuid> /data ext4 defaults,nofail 0 2' >> /etc/fstab
mount -a

nofail matters: without it an instance whose volume is detached fails to boot into anything you can SSH to, and there is no serial console on the EC2 API — you would need the web console.

Each is marked OPEN (Michael) and none has been filled with a plausible value.

  1. Whether gp3 is accepted as an alias for standard. Accepting it makes every stock Terraform module apply unchanged, which is the whole thesis of an AWS-shaped API; it also means a customer who asked for 3000 IOPS gets 48 and is told nothing. The current answer is to reject, and to lose the modules.
  2. Volume QoS (D9). The catalog’s 48 IOPS / 11 MB/s is advertised and not enforced. Enforcing it needs a block-storage QoS specification associated with the hdd volume type and a task in resources.yml to create both — today that file creates flavors only. Until then the platform’s own first rule is broken.
  3. Reconciling orphaned the block-storage service volumes created when the layer crashed between the block-storage service’s 201 and the record write (section 4). A periodic sweep of the block-storage service volumes with no vol- in the record, and what it does with them — delete, or adopt.
  4. the block-storage service states with no EC2 mapping (section 5): maintenance, backing-up, restoring-backup, retyping. An operator action can put a volume into one and the interim rule reports in-use, which is a guess.
  5. Whether volumes are billed, and at what rate. Section 10. The catalog’s pricing rule prices the AWS-equivalent instance type only; there is no storage price rule, so cd-storage-4-16-1000 was removed from the catalog for exactly this reason and attached volumes are free by accident.
  6. Snapshot storage cost and retention. A snapshot survives its volume and nothing expires it.