storage

package
v1.26.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 37 Imported by: 0

README

Storage

Purpose

pkg/handler/storage handles v2 file storage.

It is one of the strongest examples of why the saga pattern exists in this repository. Storage create and update are not just “write one CRD” operations. They coordinate:

  • request validation
  • storage-class lookup
  • attachment/network validation
  • quota allocation changes
  • storage-object mutation

So this package is where stateful lifecycle, attachment semantics, and quota accounting meet.

Distinctive Behaviour

  • create and update both use sagas with explicit compensation for quota allocation changes
  • attachments are validated against Network v2 resources in the caller's project
  • storage-class and region compatibility are validated before mutation
  • attachment IP ranges are derived from transitional provider-specific network storage-range information
  • attachment parallelism is capped to the usable network storage range; smaller non-empty storage ranges are accepted and used in full
  • attachment status rows are the union of desired and observed attachments keyed on network ID, so networks removed from the desired spec remain visible while they are still attached; each row projects observed attachment provisioning state and API-safe mount options when available
  • user-managed inline snapshot policies are optional and stored on the file storage resource as named schedules with retention.keep
  • default snapshot protection is controlled separately from user-managed snapshot policies through defaultSnapshotProtectionEnabled
  • parent File Storage create and update requests manage the user-managed snapshot policy list and default snapshot protection setting; snapshot policies do not create independent public resources or IDs
  • parent File Storage reads expose desired user-managed snapshot policy configuration in spec and projected per-policy status in status, reporting pending when no observed policy condition exists yet
  • default snapshot protection is implemented by materializing a hidden platform-managed system-default snapshot policy into the stored policy list, which the existing storage controller reconciles like any other policy (no controller change)
  • parent File Storage reads expose the resolved default snapshot protection setting but not the materialized system-default policy that implements it
  • parent File Storage create, update, and read requests expose provider-neutral POSIX ACL and atime update interval settings under the existing NFS block
  • attachment mount sources prefer the observed attachment IP range and fall back to the desired range until the controller reports an observed IP range

Invariants And Guard Rails

Manual Snapshot Lifecycle

POST /api/v2/filestorage/{filestorageID}/snapshots and DELETE /api/v2/filestorage/{filestorageID}/snapshots/{filestorageSnapshotID} record durable Region intent and return 202 Accepted. Neither request calls a provider, allocates snapshot-specific quota, or waits for backend convergence.

Snapshot operations require their independent region:filestoragesnapshots:v2 grant, not File Storage Read. Create uses parent scope and completes the principal before authorization. Delete uses child scope without requiring the parent. Visibility follows snapshot reads.

Create accepts standard resource metadata plus optional immutable expiration and protected path. The immutable, case-sensitive name uses the platform's label-value rules, is non-empty and at most 63 characters, and rejects exact . and ... Protected Path follows Snapshot Policy rules: canonical, relative, traversal-free, at most 1024 characters, with no empty value, absolute path, trailing/repeated slash, or ./.. component. Omission protects the root and requests no automatic expiration. Schema shape errors, including malformed timestamps, use the existing middleware/decoding 400 response.

Creation uses core's NewDeterministicObjectMetadata, as other deterministic Region resources do. Following Servers, Region supplies the parent File Storage UUID as the hashing namespace and the validated, exact-case name as the invariant. Core owns UUIDv5 generation and rehashes digit-leading results until the UUID starts with a letter. Snapshot IDs are opaque to callers; a raw UUIDv5 calculation is not the public contract. Golden vectors through Client.CreateSnapshot guard identity stability across dependency upgrades without duplicating core's hashing implementation.

Kubernetes creation atomically rejects an occupied parent/name slot with canonical 409, including while its previous CR is terminating. Different parents and differently cased names occupy distinct API slots. This API uniqueness does not change the provider's tenant-wide exact-name uniqueness, which is resolved asynchronously.

After the CR is fully removed, recreating the same parent/name pair deliberately reuses its API ID. The ID identifies a reusable slot, not a Kubernetes lifetime. Delayed old-lifetime create/delete operations can affect the recreated slot; customer Delete likewise uses no UID or resource-version preconditions. This unfenced same-slot race is accepted.

The create response contains the complete pending read model. Standard unobserved Available/Healthy conditions project to pending/unknown, matching the other asynchronous Region create paths; the controller writes observed conditions. The stored CR inherits organization, project, Region ancestry, parent label, API version 2, immutable creator/principal attribution, description, and tags. Before persistence it receives a same-namespace File Storage owner reference with blockOwnerDeletion: true. A parent with deletion intent rejects creation with canonical 409.

The lifecycle controller installs its finalizer before provider mutations and removes it after confirmed cleanup. Deleting an untouched CR before its first reconciliation can immediately remove it and release its slot.

Individual Delete authorizes controller cleanup of the exact-name, exact-resolved-path provider slot without creator proof. Recorded capture completion and health do not authorize skipping cleanup.

At request evaluation, spec.expirationTime <= now returns the exact canonical 404, even for paused, provisioning, errored, provisioned, or already-deleting children. The request writes no deletion intent or other resource state. A retry that crosses the deadline changes from 202 to 404 without modifying ongoing cleanup.

File Storage Delete explicitly requests foreground propagation. Its blocking snapshot children must finish cleanup first. Core's existing foregroundDeletion gate delays parent Deprovision until Kubernetes clears the cascade finalizer. A child that cannot safely complete deletion blocks normal parent teardown.

Manual Snapshot Reads

GET /api/v2/filestorage/{filestorageID}/snapshots and GET /api/v2/filestorage/{filestorageID}/snapshots/{filestorageSnapshotID} read only Region FileStorageSnapshot CRs. Manual Snapshot inventory and authorization are scoped to one parent File Storage. There is no project-wide snapshot collection or pagination. Reads never discover, adopt, or inventory provider or policy-created snapshots.

List checks Snapshot Read in the parent's project and filters by parent ID, organization/project, and per-child RBAC. Get checks the child's scope without requiring the parent. Get/Delete validate the URL parent ID against spec.fileStorageID and the parent label.

Missing or inaccessible parents return canonical 404 on List. Missing, inaccessible, wrong-parent, or expired items also return canonical 404. Other failures propagate normally; authorized empty collections return 200 [].

Tag filters use the standard core decoder and stored tag matching. Results sort by immutable, case-sensitive spec.name, then UUID; public metadata.name projects this Manual Snapshot Name rather than treating its mirrored label as rename authority.

One clock observation at the start of each storage-client read fixes request evaluation time. spec.expirationTime <= now hides the child, even if its CR still exists, is paused, or is deleting or errored. Omission means no automatic expiration. Non-expired deleting and errored resources remain readable. Expiration does not depend on status, provider calls, expirer progress, or CR deletion.

Read projection reuses core's project-scoped metadata and condition conversion. It exposes immutable spec.fileStorageId, optional expiration and relative protected path, and optional status.snapshotTime and exact status.absoluteProtectedPath. It preserves the latter byte-for-byte. Internal pause, labels, annotations, Kubernetes UID, and provider identifiers are not projected. Lifecycle producers own sanitizing condition messages; the read path projects only those standard provisioning and health conditions, never raw provider diagnostics.

Healthy=True records capture readiness observed during provisioning. After Available=True / Provisioned, health, Snapshot Time, and Absolute Protected Path remain the last provisioning observation. They do not guarantee current provider existence or usability and never authorize provider mutation. GETs use the standard middleware stack and are excluded from mutation audit logging.

Parent File Storage
  • The parent File Storage is a v2 resource with direct UUID lookup. Its Manual Snapshot children use the nested lifecycle and read contracts above.
  • Region access is enforced via region.CheckAccess during request validation, preventing callers from creating storage in regions the request organization may not use.
  • File Storage update requires File Storage update authorization before mutating storage or allocation state.
  • Quota allocation changes are part of the storage lifecycle contract, not an optional side effect.
  • Attachments must reference visible, provisioned networks in the same project.
  • Public attachment status is ordered deterministically: desired attachments first in spec order, then observed-only attachments sorted by network ID. It is omitted entirely only when both the desired and observed lists are empty.
  • Attached networks must expose a valid non-empty IPv4 storage range.
  • Snapshot policy name is the stable identity key for user-managed policies. Create requests with omitted or empty snapshotPolicies store no user-managed policies, and non-empty create lists store exactly the caller list. protectedPath is an optional canonical relative path; omission protects the File Storage root. It is immutable while a policy retains its name, though its schedule and retention remain mutable.
  • defaultSnapshotProtectionEnabled controls the hidden platform-managed baseline. It defaults to enabled when omitted on create, is preserved when omitted on update, rejects null, and is the authoritative public desired state. The handler keeps a materialized system-default entry in the stored policy list present if and only if the boolean is true (a fixed daily/04:00Z/ keep 7 schedule), so the boolean and the stored entry never drift, and that entry is never exposed in public REST responses.
  • system-default is a reserved snapshot policy name: a user-managed policy may never use it, independent of whether default snapshot protection is enabled. Server rejects any caller-supplied policy named system-default (422) on both create and update, alongside the uniqueness and schedule-shape rules. The name is reserved for the hidden platform-managed baseline, which is materialized into the stored spec only while default protection is enabled.
  • Parent File Storage update preserves existing user-managed snapshot policies when snapshotPolicies is omitted, clears them when the list is empty, and replaces them when the list is non-empty. A replacement entry whose name already exists must retain its protectedPath; attempting to add, remove, or change that path is rejected with 422 Unprocessable Content.
  • Snapshot policy mutations use File Storage authorization, are blocked while the parent File Storage object is deleting, and mutate only the parent inline desired-state list.
  • Snapshot policy primitive constraints (name pattern and the provider-safe 19-character limit, the four-policy caller maximum, optional protected-path shape, retention, and per-field schedule values) are enforced by the OpenAPI schema through the request-validation middleware before the handler runs. The handler validates rules the schema cannot express: rejecting protected-path traversal components, duplicate policy names, the reserved system-default name, and invalid schedule shapes (the interval/field combinations). The four-policy limit is caller-facing; the stored CRD list allows a fifth entry for the materialized hidden system-default baseline, which never counts against the caller maximum.
  • Update preserves the existing allocation annotation while mutating the storage resource.
  • Update uses optimistic locking. If a controller or another request modifies the File Storage after it is read, PUT returns 409 Conflict and compensates any completed quota allocation change. Callers must re-read the resource before retrying.
  • posixAcl and atimeUpdateIntervalSeconds are optional and nullable on create and update. The handler resolves omission or explicit null to false and 0 before persistence.
  • The stored NFS policy uses defaulted values. PUT never merges omitted NFS policy fields from stored state, and every GET or list result returns the complete effective policy.
  • Enabling POSIX ACLs may reduce metadata performance. Extended POSIX ACLs must be managed over NFSv3. Disabling this option does not remove existing ACLs; they may remain enforced.
  • For atimeUpdateIntervalSeconds: Set to 0 to disable read-driven atime updates. A positive value updates atime during a read only when the existing atime is older than this number of seconds.

Caveats

  • This package still depends on transitional provider-specific network status (Status.Openstack.StorageRange) because a cleaner generic source of that information does not yet exist.
  • Attachment status is an eventually-consistent projection. Spec and status are reconciled asynchronously by an out-of-service controller, so a row can report provisioned for a network already removed from the desired spec until that controller observes the detachment and prunes the row.
  • The package relies heavily on saga compensation because there is no transaction boundary for storage-plus-allocation changes.
  • Snapshot policy status is a public projection of stored per-policy conditions. API acceptance means desired state was stored; provider-side protection may still be pending until reconciliation observes it.

TODO

  • Remove the dependency on transitional provider-specific network status once a generic storage-range source exists.
  • Expand attachment status only when public API consumers need additional API-safe observed fields.

Cross-Package Context

Documentation

Index

Constants

View Source
const (
	// DefaultParallelism is the default number of IP addresses assigned to storage.
	// This maintains legacy default behaviour for storage classes that do not specify a value.
	DefaultParallelism = 4
)

Variables

View Source
var ErrAllocation = fmt.Errorf("allocation error")

Functions

This section is empty.

Types

type Client

type Client struct {
	common.ClientArgs

	// Clock supplies request evaluation time for Manual Snapshot expiration.
	Clock clock.PassiveClock
}

Client provides a restful API for storage.

func New

func New(clientArgs common.ClientArgs) *Client

New creates a new client.

func (*Client) CreateSnapshot added in v1.26.0

CreateSnapshot records immutable Manual Snapshot intent. The lifecycle controller owns the lifecycle finalizer, provider mutations, and cleanup; the request writes only the CR.

func (*Client) CreateV2

func (c *Client) CreateV2(ctx context.Context, request *openapi.StorageV2Create) (*openapi.StorageV2Read, error)

CreateV2 satisifies an http PUT action by creating a unique storage object. It does this leveraging the saga system which acts as a tape to enable rollbacks in case of errors.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, storageID regionids.FileStorageID) error

Delete satisfies the http DELETE action by removing the client. It does not leverage the saga system because we can rely on finalizers to handle this for us.

func (*Client) DeleteSnapshot added in v1.26.0

func (c *Client) DeleteSnapshot(ctx context.Context, storageID regionids.FileStorageID, snapshotID regionids.FileStorageSnapshotID) error

DeleteSnapshot records customer deletion intent without changing attribution, immutable capture intent, status, or any controller-installed finalizer.

func (*Client) Get

Get returns a storage object for a specific storageID.

func (*Client) GetRaw

func (c *Client) GetRaw(ctx context.Context, storageID string) (*regionv1.FileStorage, error)

func (*Client) GetSnapshot added in v1.26.0

GetSnapshot reads one Manual Snapshot from the Region ledger, authorizing its own project without requiring the parent to exist. It never discovers or adopts provider snapshots.

func (*Client) GetStorageClass

func (c *Client) GetStorageClass(ctx context.Context, storageClassID string) (*openapi.StorageClassV2Read, error)

func (*Client) ListSnapshots added in v1.26.0

ListSnapshots lists non-expired Manual Snapshots within an authorized File Storage. Region intent and one request-time observation determine visibility.

func (*Client) ListV2

ListV2 satisfies an http get to return all storage items within a project.

func (*Client) Update

Update satisifies an http POST action by updating the storage object attached to the storage ID. it leverages the update saga system, which acts as a tape to enable rollbacks in case of errors.

type NetworkGetter added in v1.14.0

type NetworkGetter interface {
	GetV2(ctx context.Context, networkID regionids.NetworkID) (*openapi.NetworkV2Read, error)
}

Directories

Path Synopsis
Code generated by MockGen.
Code generated by MockGen.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL