Skip to content

API Reference

Packages

metrics.simplyblock.io/v1alpha2

Package v1alpha2 holds the measurements simplyblock serves under metrics.simplyblock.io: the metrics of a logical volume, a storage cluster, a storage node, a storage device, and a storage pool.

A kind here is a sample rather than a stored object. It is computed when a client reads it, so a read returns the current numbers and there is nothing to retain. All five resources are namespaced and answer get and list only: no create, update, delete, or watch, and no field of these types is one a client sets.

v1alpha2 is the group's only version.

Resource Types

LogicalVolumeMetrics

StorageClusterMetrics

StorageDeviceMetrics

StorageNodeMetrics

StoragePoolMetrics

storage.simplyblock.io/v1alpha1

Package v1alpha1 contains API Schema definitions for the simplyblock v1alpha1 API group.

Resource Types

ReplicationOps

ReplicationOps is a one-shot user-driven CR for imperative replication operations: failover (planned or unplanned) and failback. The operator drives the backend calls to completion and records per-volume outcomes in status.results. Only one ReplicationOps may be active per ReplicationPolicy at a time, enforced via ReplicationPolicy.status.activeOpsRef.

Example:

apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationOps
metadata:
  name: string
spec:
  action: string
  scope: string
  ref: string
  sourceClusterID: string
  deleteSource: boolean
status:
  phase: string
  subphase: string
  message: string
  startedAt: Time
  completedAt: Time
  results:
    - slotRef: string
      status: string
      detail: string
      targetLvolID: string
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha1
kind string ReplicationOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ReplicationOpsSpec
status ReplicationOpsStatus

ReplicationOpsResult

ReplicationOpsResult holds the outcome for a single volume in a ReplicationOps.

Appears in: - ReplicationOpsStatus

Example:

slotRef: string
status: string
detail: string
targetLvolID: string
Field Description Default Validation
slotRef string SlotRef is the name of the ReplicationSlot CR.
status string Status is the outcome for this volume. Enum: [succeeded skipped failed]
detail string Detail is an optional human-readable note (error message or skip reason). Optional: {}
targetLvolID string TargetLvolID is the UUID of the volume on the target cluster (failover only). Optional: {}

ReplicationOpsSpec

ReplicationOpsSpec defines the desired state of a ReplicationOps.

Appears in: - ReplicationOps

Example:

action: string
scope: string
ref: string
sourceClusterID: string
deleteSource: boolean
Field Description Default Validation
action string Action is the operation to perform. Immutable.
failover: unplanned — promote target clone, source may be down.
failback: restore source as primary after a prior failover.
migration: planned cutover — calls replication_commit per volume; both clusters stay up.
State progression: replicating → cutover_pending → cutover_done.
Enum: [failover failback migration]
Required: {}
scope string Scope controls which volumes are affected. Immutable.
target: all volumes across every policy that uses the named ReplicationPair.
policy: all volumes managed by the named ReplicationPolicy CR.
volume: a single ReplicationSlot (planned or unplanned per-volume operation).
Enum: [target policy volume]
Required: {}
ref string Ref is the name of the resource identified by Scope:
a ReplicationPair name for scope=target,
a ReplicationPolicy name for scope=policy,
or a ReplicationSlot name for scope=volume. Immutable.
Required: {}
sourceClusterID string SourceClusterID is used for failback only. Omit to recover to the original source. Optional: {}
deleteSource boolean DeleteSource instructs the backend to delete the old volume after a
successful cutover. For migration it deletes the original source volume;
for failback it deletes the failed-over volume on the target cluster.
Optional: {}

ReplicationOpsStatus

ReplicationOpsStatus holds the observed state of a ReplicationOps.

Appears in: - ReplicationOps

Example:

phase: string
subphase: string
message: string
startedAt: Time
completedAt: Time
results:
  - slotRef: string
    status: string
    detail: string
    targetLvolID: string
Field Description Default Validation
phase string Phase is the current lifecycle phase of this operation. Enum: [Pending Running Succeeded Failed]
Optional: {}
subphase string Subphase describes what the operation is currently doing within the phase
(e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock").
Optional: {}
message string Message is a human-readable description of the current phase. Optional: {}
startedAt Time ⧉ StartedAt is when the operation began. Optional: {}
completedAt Time ⧉ CompletedAt is when the operation finished (successfully or not). Optional: {}
results ReplicationOpsResult array Results holds a per-volume summary of the operation outcome. Optional: {}

ReplicationPair

ReplicationPair defines the source and target clusters for a replication relationship. It is reusable configuration — multiple ReplicationPolicies may reference the same pair to replicate volumes between the same two clusters with different schedules or retention. The operator ensures the corresponding backend ReplicationTarget exists and stores its ID in status.backendTargetID for use by ReplicationPolicy resources.

Example:

apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationPair
metadata:
  name: string
spec:
  sourceCluster: string
  targetCluster: string
status:
  ready: boolean
  backendTargetID: string
  message: string
  conditions:
    - Condition
  activeOpsRef: string
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha1
kind string ReplicationPair
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ReplicationPairSpec
status ReplicationPairStatus

ReplicationPairSpec

ReplicationPairSpec defines the source and target clusters for a replication relationship.

Appears in: - ReplicationPair

Example:

sourceCluster: string
targetCluster: string
Field Description Default Validation
sourceCluster string SourceCluster is the name of the local StorageCluster (the replication source). Required: {}
targetCluster string TargetCluster is the name or UUID of the remote cluster (the replication target).
Immutable after creation.
Required: {}

ReplicationPairStatus

ReplicationPairStatus holds the observed state of a ReplicationPair.

Appears in: - ReplicationPair

Example:

ready: boolean
backendTargetID: string
message: string
conditions:
  - Condition
activeOpsRef: string
Field Description Default Validation
ready boolean Ready is true when the backend ReplicationTarget has been created and is available. Optional: {}
backendTargetID string BackendTargetID is the UUID of the backend ReplicationTarget resource. Optional: {}
message string Message provides a human-readable description of the current state. Optional: {}
conditions Condition ⧉ array Conditions holds standard Kubernetes condition types. Optional: {}
activeOpsRef string ActiveOpsRef is the name of the ReplicationOps currently holding the
target-scope lock on this pair. Only one scope=target ReplicationOps may
be active per pair at a time.
Optional: {}

ReplicationPolicy

ReplicationPolicy defines the replication schedule and retention for volumes replicated between the clusters defined by a ReplicationPair. A StorageClass or PVC references a policy via the storage.simplyblock.io/replication-policy annotation. The operator automatically creates one ReplicationSlot per bound PVC. Deletion is blocked while any ReplicationSlots reference this policy.

Example:

apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationPolicy
metadata:
  name: string
spec:
  pairRef: string
  mode: string
  interval: string
  snapshotRetention: integer
status:
  ready: boolean
  backendPolicyID: string
  slotCount: integer
  activeOpsRef: string
  conditions:
    - Condition
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha1
kind string ReplicationPolicy
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ReplicationPolicySpec
status ReplicationPolicyStatus

ReplicationPolicySpec

ReplicationPolicySpec defines the desired replication schedule and retention.

Appears in: - ReplicationPolicy

Example:

pairRef: string
mode: string
interval: string
snapshotRetention: integer
Field Description Default Validation
pairRef string PairRef is the name of the ReplicationPair that defines the source and target clusters.
Multiple ReplicationPolicies may reference the same pair with different schedules.
Required: {}
mode string Mode controls replication semantics.
failover: target is a DR standby; volumes are read-only on the target.
migration: planned online cutover to the target cluster.
failover Enum: [failover migration]
Optional: {}
interval string Interval is how often a replication snapshot is taken (e.g. "5m", "1h"). 5m Optional: {}
snapshotRetention integer SnapshotRetention is the minimum number of snapshots to retain on the target. 3 Minimum: 2
Optional: {}

ReplicationPolicyStatus

ReplicationPolicyStatus holds the observed state of a ReplicationPolicy.

Appears in: - ReplicationPolicy

Example:

ready: boolean
backendPolicyID: string
slotCount: integer
activeOpsRef: string
conditions:
  - Condition
Field Description Default Validation
ready boolean Ready is true when the backend ReplicationPolicy has been created. Optional: {}
backendPolicyID string BackendPolicyID is the UUID of the backend ReplicationPolicy resource. Optional: {}
slotCount integer SlotCount is the number of ReplicationSlot CRs currently managed by this policy. Optional: {}
activeOpsRef string ActiveOpsRef is the name of the currently running ReplicationOps CR.
Empty when no operation is in progress.
Optional: {}
conditions Condition ⧉ array Conditions holds standard Kubernetes condition types. Optional: {}

ReplicationSlot

ReplicationSlot tracks the live replication state for a single PVC. One ReplicationSlot is created per PVC by the PVCAnnotationWatcher controller when a PVC references a ReplicationPolicy via annotation. It is owned by its PVC, so deleting the PVC cascades deletion and triggers a backend detach via the slot finalizer. The ReplicationSlot reconciler drives all backend calls: attach, monitor, cutover, failover, and detach.

Example:

apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationSlot
metadata:
  name: string
spec:
  policyRef: string
  pvcRef: string
  volumeID: string
status:
  state: string
  direction: string
  sourceLvolID: string
  targetLvolID: string
  targetNQN: string
  lastReplicatedAt: Time
  message: string
  conditions:
    - Condition
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha1
kind string ReplicationSlot
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ReplicationSlotSpec
status ReplicationSlotStatus

ReplicationSlotSpec

ReplicationSlotSpec defines the identity of a per-volume replication slot.

Appears in: - ReplicationSlot

Example:

policyRef: string
pvcRef: string
volumeID: string
Field Description Default Validation
policyRef string PolicyRef is the name of the ReplicationPolicy governing this slot. Immutable. Required: {}
pvcRef string PVCRef is the name of the PVC being replicated. Immutable. Required: {}
volumeID string VolumeID is the backend lvol UUID of the source volume. Immutable.
Format: "::"
Required: {}

ReplicationSlotStatus

ReplicationSlotStatus holds the observed state of a ReplicationSlot.

Appears in: - ReplicationSlot

Example:

state: string
direction: string
sourceLvolID: string
targetLvolID: string
targetNQN: string
lastReplicatedAt: Time
message: string
conditions:
  - Condition
Field Description Default Validation
state string State is the current replication state for this slot. Enum: [attaching replicating cutover_pending cutover_done failed_over detaching error]
Optional: {}
direction string Direction is which side of the replication relationship this cluster holds. Enum: [source target]
Optional: {}
sourceLvolID string SourceLvolID is the UUID of the source volume on the source cluster. Optional: {}
targetLvolID string TargetLvolID is the UUID of the replicated volume on the target cluster. Optional: {}
targetNQN string TargetNQN is the NVMe NQN on the target cluster (populated after failover). Optional: {}
lastReplicatedAt Time ⧉ LastReplicatedAt is the timestamp of the last successful replication snapshot. Optional: {}
message string Message provides a human-readable description of the current state. Optional: {}
conditions Condition ⧉ array Conditions holds standard Kubernetes condition types. Optional: {}

storage.simplyblock.io/v1alpha2

Package v1alpha2 is the current version of the storage.simplyblock.io API group and the version the cluster stores. It declares the kinds that run simplyblock on Kubernetes: entity kinds such as StorageCluster, StorageNode, StoragePool, and StorageDevice, whose spec is the desired state simplyblock converges on, and the Ops kind beside each of them, which requests one imperative operation against its subject and reports how far that operation got.

Several of these kinds also exist as v1alpha1, under property names that have since been renamed. A manifest written against v1alpha1 is still accepted and converted, and a client that asks for v1alpha1 gets the older spelling back. A kind introduced in v1alpha2 has no v1alpha1 spelling.

Resource Types

AttachedClaim

AttachedClaim is one claim the policy currently covers, in Kubernetes terms rather than in the control plane's.

Appears in: - StorageBackupPolicyStatus

Example:

name: string
persistentVolumeName: string
lvolID: string
attachedAt: Time
Field Description Default Validation
name string Name is the PersistentVolumeClaim's name in this namespace. Required: {}
persistentVolumeName string PersistentVolumeName is the volume behind it. Optional: {}
lvolID string LvolID is the logical volume the control plane attached the policy to. It
is what a detach addresses, and it is recorded per claim so that a claim
rebound onto a new volume is detached from the old one rather than
silently left attached.
Optional: {}
attachedAt Time ⧉ AttachedAt is when the claim started matching the selector. Optional: {}

BackupCopy

BackupCopy is the copy itself: what it is, what it cost, and what it depends on.

Appears in: - StorageBackupStatus

Example:

backupID: string
s3ID: integer
size: integer
previousBackupID: string
startedAt: Time
completedAt: Time
Field Description Default Validation
backupID string BackupID is the control plane's identifier for the copy. Optional: {}
s3ID integer S3ID is the object behind the copy in the store. It is what somebody
reconciling a bill against a bucket listing matches on.
Optional: {}
size integer Size is the copy's size in bytes, which is what a bill tracks. Minimum: 0
Optional: {}
previousBackupID string PreviousBackupID names the backup this one is incremental against, where
the control plane reports one. It makes the chain legible, which matters
because deleting a backup another depends on is not obviously safe.
Optional: {}
startedAt Time ⧉ StartedAt and CompletedAt bound how long the copy took. They are the
backup's own reported interval rather than transitions the operator
observed, because the stream coalesces and a small backup can be complete
before the operator ever saw it start.
Optional: {}
completedAt Time ⧉ Optional: {}

BackupSource

BackupSource is what the volume was when the copy was taken. It is written once and never updated: the pool a volume was in when it was backed up is a fact about the backup, and rewriting it when the volume moves would destroy the only record of where the data came from. A restore reads it to know what it is restoring.

Appears in: - StorageBackupStatus

Example:

claimName: string
claimNamespace: string
persistentVolumeName: string
poolName: string
poolUUID: string
lvolID: string
lvolName: string
fsType: string
snapshotID: string
snapshotName: string
nodeID: string
clusterUUID: string
Field Description Default Validation
claimName string ClaimName and ClaimNamespace are the PersistentVolumeClaim the copy was
taken from. They are the pair a user recognizes, and the reason the claim
rather than the volume is the printed column.
Optional: {}
claimNamespace string Optional: {}
persistentVolumeName string PersistentVolumeName is the PV that was copied. Optional: {}
poolName string PoolName and PoolUUID are the StoragePool the volume was in. A restore
names its own target pool rather than defaulting to this one, because a
backup discovered from a shared store may name a pool this cluster does
not have.
Optional: {}
poolUUID string Optional: {}
lvolID string LvolID and LvolName identify the logical volume that was copied. Optional: {}
lvolName string Optional: {}
fsType string FSType is the filesystem the volume was formatted with, which a restore
needs in order to produce a mountable claim.
Optional: {}
snapshotID string SnapshotID and SnapshotName identify the snapshot the backup was taken
from.
Optional: {}
snapshotName string Optional: {}
nodeID string NodeID is the storage node the copy was read from. It belongs to the
source rather than to the copy: it says which machine held the data, which
is what somebody correlating a backup against a node failure needs.
Optional: {}
clusterUUID string ClusterUUID is the cluster the volume lived in, which differs from the
backup's own cluster when another cluster wrote it.
Optional: {}

BackupSpec

BackupSpec parameterizes the Backup action and is ignored by the others.

Appears in: - ControlPlaneOpsSpec

Example:

blobStore: string
backupName: string
Field Description Default Validation
blobStore string BlobStore is the destination, in the form the FoundationDBBackup CRD takes
it. The operator copies it through rather than interpreting it, since the
backup is the FoundationDB operator's to perform.
Required: {}
backupName string BackupName is the FoundationDBBackup to create or trigger. Absent uses the
one already configured for the cluster, and fails when there is none and
no name to create.
Optional: {}

BackupStoreSpec

BackupStoreSpec is the S3 location a cluster's backups live in, and the credentials to reach it. It is a location and nothing else: how backups are taken and what they contain are the control plane's, and this block only says where they go. It is both the target copies are written to and the inventory the operator walks to produce StorageBackup objects.

The whole block is mutable. A cluster can be created without a store and given one later, and what changes when it changes is which backups have objects, since the object set is derived from the location rather than accumulated.

Appears in: - ClusterTemplate - StorageClusterSpec

Example:

endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
Field Description Default Validation
endpoint string Endpoint is the S3 endpoint, for example, https://s3.example.com. Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$
Required: {}
bucket string Bucket is the bucket backups are written to and read from. Required: {}
region string Region is the bucket's region, for endpoints that do not imply one. Optional: {}
credentialsSecretRef LocalObjectReference ⧉ CredentialsSecretRef names the Secret holding the access key and the
secret key. It is a reference rather than the values, because a spec is
readable by anybody who can read the object.
Required: {}

BaselineColdStartPolicy

Underlying type: string

BaselineColdStartPolicy selects what happens to a node with fewer than BaselineMinSamples samples in the window: a freshly onboarded node, or one whose probe sidecar has just started.

Validation: - Enum: [Defer PartialWindow]

Appears in: - VolumeAutoPlacementSettings

Field Description
Defer BaselineColdStartDefer omits an under-sampled node from the cycle: it is
neither a migration source nor a target until it has accumulated enough
samples, which avoids acting on a noisy baseline.
PartialWindow BaselineColdStartPartialWindow computes the baseline from whatever
samples exist, accepting a noisier baseline early on so that rebalancing
engages sooner. It is the default.

BaselineStrategy

Underlying type: string

BaselineStrategy selects how the per-node latency baseline, the denominator of the rebalancing deviation signal, is derived.

Validation: - Enum: [Benchmark RollingWindow]

Appears in: - VolumeAutoPlacementSettings

Field Description
Benchmark BaselineStrategyBenchmark uses the one-shot fio measurement taken on a
fresh cluster and frozen on the node's status. It is simple and tends to
read too low, because an idle cluster is far faster than a loaded one and
every loaded node then shows a large deviation.
RollingWindow BaselineStrategyRollingWindow derives the baseline from a rolling window
of the probe sidecar's latency series, using an outlier-rejecting
estimator. It reflects each node's recent operating latency rather than
an idle measurement, and is the default.

CancelTaskSpec

CancelTaskSpec parameterizes the CancelTask action.

Appears in: - StorageClusterOpsSpec

Example:

taskID: string
Field Description Default Validation
taskID string TaskID names the entry of StorageCluster.status.tasks to cancel, by the
control plane's identifier for it rather than by its position in the
list.
Required: {}

CapacityThresholdSpec

CapacityThresholdSpec is one capacity alarm level, as a figure for used capacity and one for provisioned capacity.

Appears in: - StorageClusterSpec

Example:

capacity: integer
provisionedCapacity: integer
Field Description Default Validation
capacity integer Capacity is the used-capacity threshold. Optional: {}
provisionedCapacity integer ProvisionedCapacity is the provisioned-capacity threshold. Optional: {}

ClusterDeploymentConfig

ClusterDeploymentConfig is a whole simplyblock deployment written down as one reviewable document: the environment, the cluster, and the node sets with their workers, interfaces, and devices. An administrator reviews it, approves it, and the operator expands it into a StorageCluster and its StorageNodes.

It is ephemeral. Everything the expansion produces is self-describing, so the document can be edited or deleted once it has been expanded, and nothing reads it afterward.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: ClusterDeploymentConfig
metadata:
  name: string
spec:
  approved: boolean
  environment: KubernetesEnvironment
  hostOS:
    distro: '^[a-z0-9][a-z0-9._-]*$'
    family: HostOSFamily
  edgeCluster: boolean
  clusterRef: string
  cluster:
    name: string
    maxSubsystemCount: integer
    vcpuCount: integer
    minHugePagesSize: string
    enableDriveFormat: boolean
    enableJournalDevice: boolean
    socketsToUse:
      - string
    nodesPerSocket: integer
    initContainerResources: ResourceRequirements
    tolerations:
      - Toleration
    containerResources: ResourceRequirements
    nodeProvisioningBudget: integer
    enableChecksumValidation: boolean
    enableAtomicity4K: boolean
    stripe:
      dataChunks: integer
      parityChunks: integer
    fabricType: string
    openshift:
      machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
    ports:
      nvmf: integer
      rpc: integer
      nodeAgent: integer
    enableFailureDomains: boolean
    enableNodeAffinity: boolean
    backup:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
      bucket: string
      region: string
      credentialsSecretRef: LocalObjectReference
    kms:
      vault:
        endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  images:
    nodeAgent:
      image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
      imagePullPolicy: PullPolicy
    spdk:
      image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
      imagePullPolicy: PullPolicy
    spdkProxy:
      image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
      imagePullPolicy: PullPolicy
  nodeSets:
    - name: string
      groups:
        - name: string
          workers:
            - string
          mgmtInterface: string
          dataInterfaces:
            - string
          devices:
            nvme:
              - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
            block:
              - '^/dev/[a-zA-Z0-9._/-]+$'
          failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
          spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
          reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
          journalManager:
            count: integer
            percentPerDevice: integer
status:
  phase: ClusterDeploymentConfigPhase
  step: KubeSnapshot
  clusterRef: string
  nodeRefs:
    - string
  message: string
  observedGeneration: integer
  expansionStartedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string ClusterDeploymentConfig
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ClusterDeploymentConfigSpec
status ClusterDeploymentConfigStatus

ClusterDeploymentConfigPhase

Underlying type: string

ClusterDeploymentConfigPhase is where the operator has got to with this document.

Validation: - Enum: [Draft Expanding Expanded Failed]

Appears in: - ClusterDeploymentConfigStatus

Field Description
Draft ClusterDeploymentConfigPhaseDraft is an unapproved document. It is
validated on every reconcile and expanded on none.
Expanding ClusterDeploymentConfigPhaseExpanding is an approved document being
turned into a cluster and its nodes.
Expanded ClusterDeploymentConfigPhaseExpanded is a document whose objects exist.
Failed ClusterDeploymentConfigPhaseFailed is a document the expansion refused or
could not finish.

ClusterDeploymentConfigSpec

ClusterDeploymentConfigSpec is a whole simplyblock deployment as one reviewable document.

The third rule is the device class one. A document describes one cluster and a cluster is built out of one class of backend storage, so every group of every node set names the same member of its DeviceSelection. The expansion reads the class off them and stamps it onto the cluster it creates, which is why the document carries no field for it.

Appears in: - ClusterDeploymentConfig

Example:

approved: boolean
environment: KubernetesEnvironment
hostOS:
  distro: '^[a-z0-9][a-z0-9._-]*$'
  family: HostOSFamily
edgeCluster: boolean
clusterRef: string
cluster:
  name: string
  maxSubsystemCount: integer
  vcpuCount: integer
  minHugePagesSize: string
  enableDriveFormat: boolean
  enableJournalDevice: boolean
  socketsToUse:
    - string
  nodesPerSocket: integer
  initContainerResources: ResourceRequirements
  tolerations:
    - Toleration
  containerResources: ResourceRequirements
  nodeProvisioningBudget: integer
  enableChecksumValidation: boolean
  enableAtomicity4K: boolean
  stripe:
    dataChunks: integer
    parityChunks: integer
  fabricType: string
  openshift:
    machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
  ports:
    nvmf: integer
    rpc: integer
    nodeAgent: integer
  enableFailureDomains: boolean
  enableNodeAffinity: boolean
  backup:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
    bucket: string
    region: string
    credentialsSecretRef: LocalObjectReference
  kms:
    vault:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
images:
  nodeAgent:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    imagePullPolicy: PullPolicy
  spdk:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    imagePullPolicy: PullPolicy
  spdkProxy:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    imagePullPolicy: PullPolicy
nodeSets:
  - name: string
    groups:
      - name: string
        workers:
          - string
        mgmtInterface: string
        dataInterfaces:
          - string
        devices:
          nvme:
            - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
          block:
            - '^/dev/[a-zA-Z0-9._/-]+$'
        failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
        spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
        reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
        journalManager:
          count: integer
          percentPerDevice: integer
Field Description Default Validation
approved boolean Approved is the review gate. A document is expanded only once it is set,
and is validated but otherwise inert before that, which is what makes
reviewing a wrong document safe.
It is defaulted and serialized rather than omitted when false, and the
two are the same requirement read twice. A reviewer has to see the gate
they are being asked to open, and the rules above have to find the field
they read: a bool omitted when false is a key the apiserver never stores,
so a rule reading it fails rather than reading false, and the first rule
guarding approval denied every approval there could ever be. The has()
guards are what carry documents written before the default existed.
false Optional: {}
environment KubernetesEnvironment Environment is the Kubernetes distribution this deployment targets. It is a
shorthand the expansion spends: it sets enableKubeletConfiguration,
enableCpuTopology, and openShiftCluster on the cluster the document
produces, after which nothing reads it again. The worker's host OS is not
among them and is stated in hostOS, because a distribution decides what
Kubernetes does to a machine and not which packages the machine has.
Enum: [Vanilla OpenShift Rancher K3s Talos]
Optional: {}
hostOS HostOSSpec HostOS is the operating system the workers run, which decides what the
host itself offers rather than what Kubernetes does to it. The expansion
spends the distro on StorageCluster.spec.storageNodes.ubuntuHost and
carries the family for the reviewer reading the document.
Optional: {}
edgeCluster boolean EdgeCluster states that this is an edge deployment. An edge deployment
differs from a datacenter one in topology and scale rather than in kind,
so it is the same StorageCluster with fewer, smaller nodes.
Optional: {}
clusterRef string ClusterRef names an existing StorageCluster this document adds nodes to.
Absent means the document creates the cluster in Cluster. Setting it to a
cluster that does not exist, or leaving it absent when one already does,
is refused rather than reconciled.
The maximum is what a StorageCluster name may be and not the 253 an object
name may be: a reference between the two names nothing that can exist.
MaxLength: 63
Optional: {}
cluster ClusterTemplate Cluster is the StorageCluster to create. Ignored when ClusterRef is set. Optional: {}
images DeploymentImages Images are the container images this deployment pins. Unstated, each field
the expansion would write keeps its own default.
Optional: {}
nodeSets NodeSet array NodeSets are the nodes the deployment is made of. At least one, and at
most 64.
MaxItems: 64
MinItems: 1
Required: {}

ClusterDeploymentConfigStatus

ClusterDeploymentConfigStatus is the observed state of the document.

Appears in: - ClusterDeploymentConfig

Example:

phase: ClusterDeploymentConfigPhase
step: KubeSnapshot
clusterRef: string
nodeRefs:
  - string
message: string
observedGeneration: integer
expansionStartedAt: Time
Field Description Default Validation
phase ClusterDeploymentConfigPhase Phase is the operator's own view of the document. Enum: [Draft Expanding Expanded Failed]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the expansion machine within Expanding. Optional: {}
clusterRef string ClusterRef names the StorageCluster the expansion produced or added to. It
is a record rather than a dependency: nothing resolves it after expansion,
which is what makes the document safe to delete.
Optional: {}
nodeRefs string array NodeRefs names the StorageNode objects the expansion created, for the same
reason.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the document moves, and never a log. On a Draft it is what validation
found, which is what a reviewer reads before approving.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
expansionStartedAt Time ⧉ ExpansionStartedAt is when the expansion machine was born, which is the
first reconcile after the document was approved. A document may sit as a
draft for as long as a review takes, so this is not creationTimestamp and
the difference is the whole point: how long a deployment takes is measured
from the moment somebody said yes.
It is the start of the expansion_duration_seconds metric, whose histogram
needs an instant that survives the operator restarting mid-expansion,
which nothing in memory and no step deadline supplies.
Optional: {}

ClusterPortsSpec

ClusterPortsSpec is where a cluster's storage nodes listen.

The three are one block here and three fields on the StorageCluster, which is the one place the document deliberately does not mirror the cluster's shape. They are the same decision taken once — which ports this deployment's nodes bind — and a reviewer reads them together or not at all, where the cluster carries them flat because that is what shipped.

Every member is immutable on the cluster: a node binds its ports when it starts and the control plane hands them out from the bases it was given at cluster create, so the document is the only place any of them can be stated.

Each carries the control plane's own default, so a document that names the block shows a reviewer the three numbers a cluster will actually run with rather than three blanks they would have to know the backend to fill in.

Appears in: - ClusterTemplate

Example:

nvmf: integer
rpc: integer
nodeAgent: integer
Field Description Default Validation
nvmf integer NVMf is the base of the NVMe-oF port range every node binds. It expands
into StorageCluster.spec.nvmfBasePort.
4420 Maximum: 65535
Minimum: 1024
Optional: {}
rpc integer Rpc is the base of the RPC port range every node binds. It expands into
StorageCluster.spec.rpcBasePort.
8080 Maximum: 65535
Minimum: 1024
Optional: {}
nodeAgent integer NodeAgent is the port each node's agent API listens on. It expands into
StorageCluster.spec.snodeApiPort, and it is named for the component
rather than for that field: the agent is what spec.images.nodeAgent pins
and what the storage-node DaemonSet runs.
50001 Maximum: 65535
Minimum: 1024
Optional: {}

ClusterTask

ClusterTask is one asynchronous job the control plane is running, as of the last frame of the task stream. It is a window rather than a record: a task that reaches a terminal outcome leaves status.tasks, and what remains of it is an event.

It carries no progress figure and no creation date, because the control plane reports neither, which is why the list is in the control plane's order rather than newest first.

Appears in: - StorageClusterStatus

Example:

id: string
type: string
status: string
retry: integer
Field Description Default Validation
id string ID is the control plane's identifier, and it is how a CancelTask
operation names the task. A position in the list is not an identity,
because the next frame may order it differently.
Required: {}
type string Type is what kind of job it is, in the control plane's own spelling: the
value is the backend's rather than this API's.
Optional: {}
status string Status is the control plane's own status string, and it is why this entry
carries no phase: the operator adds nothing to what the backend reports.
Its values are new, running, suspended, and done, of which only the
first three appear here.
Optional: {}
retry integer Retry is how many times the control plane has restarted this task. It is
the one number that separates a task that is slow from one that is
failing, and it is the closest thing the schema has to progress.
Minimum: 0
Optional: {}

ClusterTemplate

ClusterTemplate is the StorageCluster the expansion creates, where it creates one. It carries the layout fields a cluster cannot change later, so that a reviewer sees them before the cluster exists rather than after.

Appears in: - ClusterDeploymentConfigSpec - StorageSiteDraft

Example:

name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
  - string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
  - Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
  dataChunks: integer
  parityChunks: integer
fabricType: string
openshift:
  machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
  nvmf: integer
  rpc: integer
  nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
  endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  bucket: string
  region: string
  credentialsSecretRef: LocalObjectReference
kms:
  vault:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
Field Description Default Validation
name string Name is the StorageCluster's name, and is therefore held to what such a
name may be rather than to what an object name may be. A longer value is a
document the API server accepts and a CreatingCluster step that can never
succeed, since the cluster it would write is one the API server refuses.
MaxLength: 63
Required: {}
maxSubsystemCount integer MaxSubsystemCount is the maximum number of NVMe-oF subsystems each storage
node of this cluster serves. Required, because the StorageCluster's own
field is, and no StorageNode carries a copy of it.
Maximum: 75
Minimum: 10
Required: {}
vcpuCount integer VCPUCount is the number of vCPUs allocated to SPDK on each storage node of
this cluster. It is stated here and nowhere below, because the control
plane assumes it uniform across a cluster's nodes; CreatingNodes copies it
into every StorageNode.spec.config.sizing it writes. Required, because the
StorageCluster's own field is.
The floor is 4 rather than a hardware limit: a node must carry one core
beyond this budget for the system, and the control plane's core layout
assigns no NVMe-oF poller core at all for a 2-vCPU budget.
Minimum: 4
Required: {}
minHugePagesSize string MinHugePagesSize is the smallest huge-page allocation each storage node of
this cluster makes: 100G or 1T, where a bare number is gigabytes. Like
VCPUCount it is the cluster's and is copied onto every node the expansion
writes. Omitted, each node uses the computed minimum.
MaxLength: 32
Optional: {}
enableDriveFormat boolean EnableDriveFormat formats every device the document names before a storage
node takes it, which is how a drive carrying anything already is made
usable.
It says what is wanted rather than how, because the how differs by device
class: an NVMe device is formatted to a 4K block size, and a logical block
device has its signatures wiped. One field covers both, so a document does
not have to know which class the expansion will resolve it to.
It is on the document rather than defaulted further down because it is
destructive and the document is what somebody approves. A reviewer reading
a draft has to see that the drives it lists will be formatted, and be able
to strike it before approving; the cluster's own field is immutable once
the cluster exists, so a default nobody saw could not be undone either.
Optional: {}
enableJournalDevice boolean EnableJournalDevice dedicates the smallest NVMe device on each of this
deployment's workers to the journal manager, instead of carving a journal
partition out of every device.
It is here rather than on a node set because it is immutable on the cluster
it lands on, for the reason SocketsToUse is: the on-disk layout a fleet was
built with is not one a later document can vary. It also costs a drive of
capacity per node, which is a trade a reviewer approves rather than one a
default makes for them.
Optional: {}
socketsToUse string array SocketsToUse restricts the deployment to selected NUMA sockets, and empty
means socket 0 alone. With NodesPerSocket it decides how many storage nodes
each worker runs, so a group of two workers on a two-socket layout expands
to four nodes.
It is here rather than on a node set because it is immutable on the cluster
it lands on: the layout a fleet was built with is not one a later document
can vary, and a reviewer should see it before the cluster exists.
MaxItems: 16
items:MaxLength: 16
Optional: {}
nodesPerSocket integer NodesPerSocket is how many storage nodes run per NUMA socket. See
SocketsToUse, which it multiplies.
Maximum: 8
Minimum: 1
Optional: {}
initContainerResources ResourceRequirements ⧉ InitContainerResources sizes both of the storage node's init containers,
and expands into the cluster's own spec.storageNodes.initContainerResources.
They are sized apart from the container because they do a different job
and are gone before it starts: one writes the node's env file and the
other runs node_configure.py once, so what they need is a short burst
rather than the footprint of a process that runs for the node's life.
Stating either half replaces both, as with containerResources, and it is
a pointer for the same reason.
Optional: {}
tolerations Toleration ⧉ array Tolerations are what the storage-node pods tolerate, and they expand into
the cluster's own spec.storageNodes.tolerations.
A fleet that dedicates machines to storage taints them, which is what
keeps everything else off. The DaemonSet that lands on those machines has
to tolerate the taint or it schedules nowhere, and a document that could
not say so described a deployment that does not start: the correction was
an edit to the cluster the document had just created, on a field the
document owns everywhere else.
A growth document states none. It names a cluster rather than describing
one, and that cluster already carries what its storage nodes tolerate.
MaxItems: 32
Optional: {}
containerResources ResourceRequirements ⧉ ContainerResources sizes the storage-node container, and expands into the
cluster's own spec.storageNodes.containerResources.
The container it sizes is the node's management API rather than SPDK,
which runs in a pod of its own: what outgrows the default is a node
answering for many subsystems, not a node moving more data. It is on the
document because a deployment is where a fleet's sizing is decided, and
a cluster written from a document that could not say so had to be edited
afterward on a field the document owns everywhere else.
Stating either half replaces both. The defaults apply to a cluster that
states neither requests nor limits, so a document stating requests alone
produces a container with no limits rather than one with the default
limits, and a memory limit is what has the kubelet evict a leaking agent
rather than losing the worker.
It is a pointer because a resource block is a struct, and a struct with
omitempty is serialized whether or not anything is in it: as a value,
every document a discovery run writes would carry an empty
containerResources that says nothing and that a reviewer has to decide
about.
Optional: {}
nodeProvisioningBudget integer NodeProvisioningBudget is how many workers the expansion may have in the
node-add process at once. It expands into the cluster's own
spec.storageNodes.nodeProvisioningBudget, whose meaning it shares: the cap
is counted by distinct worker, so a two-socket host spends one of the
budget, and a worker hosting a FoundationDB pod is sequential whatever the
budget says.
It is on the document because a document is what states the size of a
deployment, and a deployment of thirty workers added one at a time is the
difference between an afternoon and a week. Omitted, the cluster's default
of one applies, which is the serial behavior.
Minimum: 1
Optional: {}
enableChecksumValidation boolean EnableChecksumValidation turns on inline CRC validation of every I/O, for
silent-data-error protection.
It is on the document because it is immutable on the cluster it lands on:
the backend bakes the checksum method into each device when the cluster is
created and never re-applies it, so a cluster created without this is one
nobody can turn it on for. A deployment that wants its data checked has to
say so here or not at all.
Optional: {}
enableAtomicity4K boolean EnableAtomicity4K enforces 4K write atomicity on every device this
deployment names, which is what lets checksum validation run on devices
whose logical block size is under the data plane's 4K minimum.
It is the route to checked I/O on a device that cannot be reformatted: a
logical block device's block size is fixed by the drive, and some NVMe
devices offer no 4K format either. Where a device can be reformatted,
EnableDriveFormat is the other route and this is unnecessary.
It is an enforcement because the question is often unanswerable. A SATA
drive presenting 512-byte logical blocks over a 4K physical sector reports
512 and nothing more, and a kernel older than 6.11 publishes no atomic
write attributes at all. Where a device does answer, the storage node's
report carries it, and a reviewer approves this against that rather than
against a vendor's datasheet -- because enforcing a guarantee the hardware
does not keep is how a torn write becomes a checksum that silently
disagrees with it.
It means nothing unless EnableChecksumValidation is set, which is the
cluster's own rule and is left to the cluster to enforce.
Optional: {}
stripe StripeSpec Stripe is the erasure-coding layout. Optional: {}
fabricType string FabricType is the storage fabric. MaxLength: 32
Optional: {}
openshift OpenShiftSpec OpenShift is what this deployment states because it runs on OpenShift. It
expands into StorageCluster.spec.storageNodes.openshift, whose shape it
shares, and it is read only for a document whose environment is
OpenShift: the environment is what says which distribution this is, and
the block is what that distribution needs said beyond it.
Optional: {}
ports ClusterPortsSpec Ports are where this cluster's storage nodes listen. Unstated, and for
each member left unstated, the cluster's own defaults decide.
Optional: {}
enableFailureDomains boolean EnableFailureDomains opts the cluster into failure-domain mode, in which
every group must label the fault group its workers belong to.
Optional: {}
enableNodeAffinity boolean EnableNodeAffinity has the data plane serve an erasure-coded volume's I/O
from the local node's own devices where it can, before crossing the
network.
It is not Kubernetes affinity, and the name is the one place this API
invites that reading: nothing about it schedules a pod, labels a worker,
or places a volume's primary node. The control plane carries it into the
cluster map it pushes to each node, where it sets the local node's index,
and what changes is which copy of a chunk is read.
Co-locating a workload with the primary node of its volume is a separate
mechanism and is not configured here.
It is on the document because it is immutable on the cluster: the control
plane takes it at cluster create and never re-applies it, so this is the
only moment it can be set at all.
Optional: {}
backup BackupStoreSpec Backup is where this cluster's backups live, and it expands into
StorageCluster.spec.backup unchanged.
It is here for the reason KMS is: a store stated on the document is
present when the cluster is created rather than patched in afterward by
whoever remembers. Unlike most of what this template carries, the field it
fills is mutable, so a document that states none costs nothing permanent.
A cluster can be given a store whenever there is one to give.
The Secret it names is not resolved at admission. It is a core object a
deployment legitimately creates alongside the document or after it, and
the cluster's own creation is where its absence is reported.
Optional: {}
kms KMSSpec KMS selects where the cluster stores volume encryption keys. Stating it on
the document is what makes it present when the cluster is created, where
setting it on the StorageCluster afterward races with that creation.
Optional: {}

ControlPlane

ControlPlane is the simplyblock control plane for one Kubernetes cluster: FoundationDB together with the management API, either installed by the operator or already existing. It is a singleton named simplyblock, and it is the root of the ownership spine: nothing else in this API group reconciles meaningfully before it reports Available.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: ControlPlane
metadata:
  name: string
spec:
  source:
    local:
      image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
      imagePullPolicy: PullPolicy
      foundationDB:
        replicas: integer
        storageClassName: string
        resources: ResourceRequirements
        nodeSelector:
          string: string
      replicas: integer
      resources: ResourceRequirements
      tolerations:
        - Toleration
      nodeSelector:
        string: string
      tls:
        enableTLS: boolean
        enableMutualTLS: boolean
        provider: ControlPlaneTLSProvider
      adminTokenSecretRef: LocalObjectReference
      observability:
        enableMonitoring: boolean
        secretRef: LocalObjectReference
    managed:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
      credentialsSecretRef: LocalObjectReference
      caBundleSecretRef: LocalObjectReference
      storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
status:
  phase: ControlPlanePhase
  step: KubeSnapshot
  endpoint: string
  version: string
  lastChecked: Time
  components:
    - name: string
      desired: integer
      ready: integer
      essential: boolean
  activeOpsRef: string
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string ControlPlane
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata. Optional: {}
spec ControlPlaneSpec Optional: {}
status ControlPlaneStatus Optional: {}

ControlPlaneComponentStatus

ControlPlaneComponentStatus is one workload of a managed control plane and how much of it is running. The phase is the worst verdict across these and the readiness probe, and only an essential component at zero ready can make it Unavailable.

Appears in: - ControlPlaneStatus

Example:

name: string
desired: integer
ready: integer
essential: boolean
Field Description Default Validation
name string Name is the workload's name, as applied. Required: {}
desired integer Desired is how many replicas the component should have. For the component
carrying its own operator it is that resource's own count, because a
FoundationDBCluster reports quorum rather than replicas.
Minimum: 0
Optional: {}
ready integer Ready is how many of them are. Minimum: 0
Optional: {}
essential boolean Essential states whether this component at zero ready makes the control
plane Unavailable rather than Degraded. It is decided by the operator
rather than by a user, and it is reported here so that a phase can be
explained.
Optional: {}

ControlPlaneObservability

ControlPlaneObservability is whether the control plane provisions the monitoring stack, and the credential it does that with.

The control plane provisions it once, when the first storage cluster is created: it opens the Graylog input the log collector ships to, creates the Grafana user every cluster's dashboards use, and widens the OpenSearch result window. It records the decision with the deployment, so enabling monitoring after the first cluster exists sets the environment but does not provision anything.

Appears in: - LocalControlPlane

Example:

enableMonitoring: boolean
secretRef: LocalObjectReference
Field Description Default Validation
enableMonitoring boolean EnableMonitoring provisions the monitoring stack when the first storage
cluster is created. Unset is off.
Optional: {}
secretRef LocalObjectReference ⧉ SecretRef names a Secret in this namespace holding the monitoring stack's
admin password under the key MONITORING_SECRET. Required when
EnableMonitoring is set, and its name must not be empty. A pod naming a
Secret that does not exist does not start.
Optional: {}

ControlPlaneOps

ControlPlaneOps is a single operation performed against the control plane. It runs to a terminal phase and stays afterward as the audit record of what was done, with which parameters, and how it ended. Only one may be active per control plane at a time, which the entity's status.activeOpsRef enforces.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: ControlPlaneOps
metadata:
  name: string
spec:
  controlPlaneRef: string
  action: ControlPlaneOpsAction
  abort: boolean
  upgrade:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  restart:
    components:
      - string
  backup:
    blobStore: string
    backupName: string
status:
  phase: ControlPlaneOpsPhase
  step: KubeSnapshot
  message: string
  backupRef: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string ControlPlaneOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec ControlPlaneOpsSpec
status ControlPlaneOpsStatus

ControlPlaneOpsAction

Underlying type: string

ControlPlaneOpsAction is the operation a ControlPlaneOps performs. Every action acts on what the operator installed, so every action requires a managed control plane. An operation naming a control plane the operator did not install is rejected at creation rather than created and failed.

Validation: - Enum: [Restart Upgrade Backup]

Appears in: - ControlPlaneOpsSpec

Field Description
Restart ControlPlaneOpsActionRestart recycles a wedged workload. Its scope is
spec.restart.components, and an empty list recycles the whole control
plane.
Upgrade ControlPlaneOpsActionUpgrade moves the control plane to a new version and
verifies afterward that the version it reports is the one asked for.
Backup ControlPlaneOpsActionBackup asks FoundationDB for a backup outside
whatever schedule exists. It asks rather than implements: the snapshot is
the FoundationDB operator's to take.

ControlPlaneOpsPhase

Underlying type: string

ControlPlaneOpsPhase is the operation's own progress.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - ControlPlaneOpsStatus

Field Description
Pending ControlPlaneOpsPhasePending is an operation waiting for its target's lock.
Running ControlPlaneOpsPhaseRunning is an operation holding the lock and working.
Succeeded ControlPlaneOpsPhaseSucceeded is a finished operation that did what it
said.
Failed ControlPlaneOpsPhaseFailed is a finished operation that did not.
Aborted ControlPlaneOpsPhaseAborted is an operation stopped on request, whose
unwind has finished.

ControlPlaneOpsSpec

ControlPlaneOpsSpec is one operation to perform against the control plane.

Everything except spec.abort is frozen once the object is admitted, which is what makes the status an audit of the request that ran rather than of whatever the object says now. The parameters are consumed several steps apart: Preflight reads spec.upgrade.image and Applying writes it, and Draining reads spec.restart.components before Restarting recycles them. An edit in between produces an operation that checked one thing and did another.

What is frozen is each parameter block's presence together with its contents.

Appears in: - ControlPlaneOps

Example:

controlPlaneRef: string
action: ControlPlaneOpsAction
abort: boolean
upgrade:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
restart:
  components:
    - string
backup:
  blobStore: string
  backupName: string
Field Description Default Validation
controlPlaneRef string ControlPlaneRef names the ControlPlane this operation acts on, in this
object's own namespace. The operation never owns its target, because
deleting the record of an operation must not delete the control plane it
operated on.
Required: {}
action ControlPlaneOpsAction Action is the operation to perform. Immutable, so that the status describes
the operation that ran.
Enum: [Restart Upgrade Backup]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind. It is
the one field of this spec an update may change, because it is the one that
is meant to be set after the operation started. Whether an abort is
expressible from the current step is declared by that action's graph rather
than checked here.
Optional: {}
upgrade UpgradeSpec Upgrade parameterizes action Upgrade and is ignored by the others. Optional: {}
restart RestartSpec Restart parameterizes action Restart and is ignored by the others. Optional: {}
backup BackupSpec Backup parameterizes action Backup and is ignored by the others. Optional: {}

ControlPlaneOpsStatus

ControlPlaneOpsStatus is the observed state of one control-plane operation.

Appears in: - ControlPlaneOps

Example:

phase: ControlPlaneOpsPhase
step: KubeSnapshot
message: string
backupRef: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase ControlPlaneOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. It is
persisted before the side effect that step performs, so a step reported
here is a step that started.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
backupRef string BackupRef names the FoundationDBBackup a Backup run created or triggered.
The operation does not own it, because deleting the record of a backup
must not delete the backup's configuration.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

ControlPlanePhase

Underlying type: string

ControlPlanePhase is where the operator has got to with this control plane.

Validation: - Enum: [Installing Available Degraded Unavailable]

Appears in: - ControlPlaneStatus

Field Description
Installing ControlPlanePhaseInstalling is a control plane that has not worked yet.
Available ControlPlanePhaseAvailable is one whose readiness probe passes and whose
workload pods are settled.
Degraded ControlPlanePhaseDegraded is one whose readiness probe passes while a
management API or FoundationDB pod is restarting. It answers every
request, so nothing holds on it and it exists to be read by a person. A
control plane the operator does not manage never reaches it, because the
operator owns no pods there to watch.
Unavailable ControlPlanePhaseUnavailable is one whose readiness probe fails: it
worked and stopped, which is a different situation from one that never
started, and it is what downstream controllers hold on. The word claims
only what the probe observed, since a failing probe cannot tell a crashed
process from a wedged one or from a partition.

ControlPlaneSource

ControlPlaneSource selects whether this cluster hosts its control plane or is managed by one elsewhere. Exactly one member is set, which is what makes the two modes siblings rather than two unrelated top-level fields.

Which member it is cannot change afterward, while the members themselves stay editable: changing spec.source.local.image is an ordinary edit, and it is what a ControlPlaneOps upgrade performs.

Appears in: - ControlPlaneSpec

Example:

local:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
  foundationDB:
    replicas: integer
    storageClassName: string
    resources: ResourceRequirements
    nodeSelector:
      string: string
  replicas: integer
  resources: ResourceRequirements
  tolerations:
    - Toleration
  nodeSelector:
    string: string
  tls:
    enableTLS: boolean
    enableMutualTLS: boolean
    provider: ControlPlaneTLSProvider
  adminTokenSecretRef: LocalObjectReference
  observability:
    enableMonitoring: boolean
    secretRef: LocalObjectReference
managed:
  endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  credentialsSecretRef: LocalObjectReference
  caBundleSecretRef: LocalObjectReference
  storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
Field Description Default Validation
local LocalControlPlane Local is a control plane the operator installs. Optional: {}
managed ManagedControlPlane Managed is a control plane that already exists. Optional: {}

ControlPlaneSpec

ControlPlaneSpec is the desired state of the simplyblock control plane for one namespace.

Appears in: - ControlPlane

Example:

source:
  local:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    imagePullPolicy: PullPolicy
    foundationDB:
      replicas: integer
      storageClassName: string
      resources: ResourceRequirements
      nodeSelector:
        string: string
    replicas: integer
    resources: ResourceRequirements
    tolerations:
      - Toleration
    nodeSelector:
      string: string
    tls:
      enableTLS: boolean
      enableMutualTLS: boolean
      provider: ControlPlaneTLSProvider
    adminTokenSecretRef: LocalObjectReference
    observability:
      enableMonitoring: boolean
      secretRef: LocalObjectReference
  managed:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
    credentialsSecretRef: LocalObjectReference
    caBundleSecretRef: LocalObjectReference
    storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
Field Description Default Validation
source ControlPlaneSource Source selects whether this cluster hosts its control plane or is managed
by one elsewhere. Switching a live deployment between the two is not a
reconfiguration, because the clusters and their volumes live in the
FoundationDB behind the old one, so which mode is chosen is frozen at
creation. What is inside the chosen mode stays editable.
Both rules are declared on ControlPlaneSource rather than here. See the
type for why.
Required: {}

ControlPlaneStatus

ControlPlaneStatus is the observed state of the control plane.

Appears in: - ControlPlane

Example:

phase: ControlPlanePhase
step: KubeSnapshot
endpoint: string
version: string
lastChecked: Time
components:
  - name: string
    desired: integer
    ready: integer
    essential: boolean
activeOpsRef: string
message: string
observedGeneration: integer
Field Description Default Validation
phase ControlPlanePhase Phase is the operator's own view of the control plane. Enum: [Installing Available Degraded Unavailable]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the installation machine within Installing. The
rule repeats the ControlPlaneStep enum because a marker cannot reach a
field of the shared snapshot type.
Optional: {}
endpoint string Endpoint is the resolved management API base URL, derived in the local
case and echoed in the managed one. It is what every controller in the
operator reads to reach the control plane, so that one object answers
where it is.
Optional: {}
version string Version is the version the management API reports. Optional: {}
lastChecked Time ⧉ LastChecked is when the readiness probe last ran. Optional: {}
components ControlPlaneComponentStatus array Components is the per-component readiness the phase is derived from, one
entry per workload the managed install applies. It is empty for a remote
control plane, which has no components the operator owns. Without it a
Degraded phase says that something is wrong and not what.
Optional: {}
activeOpsRef string ActiveOpsRef names the ControlPlaneOps currently allowed to act on this
control plane. Empty when none is running.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the control plane moves, and never a log. On a failed probe it is the
control plane's own error rather than a paraphrase of it.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

ControlPlaneTLS

ControlPlaneTLS is how a control plane this operator installs serves, and what it requires of the clients that reach it.

The field names are DriverTLS's, because they are the same three decisions about the same connection seen from its two ends, and a reader who knows one should not have to learn the other. What differs is the default: the CSI driver's fields were added to describe deployments that already existed and default off, and these default on. An installed control plane holds every cluster definition, node registration, and volume record, and is reached over the pod network by the operator, the CSI driver, and the metrics scrape alike, so plaintext is a decision to state rather than the state a deployment lands in by leaving the block out.

Appears in: - LocalControlPlane

Example:

enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
Field Description Default Validation
enableTLS boolean EnableTLS serves the management API over TLS. Unset is on. true Optional: {}
enableMutualTLS boolean EnableMutualTLS additionally requires a caller to present a certificate
of its own, rather than reaching the control plane anonymously over the
encrypted connection EnableTLS alone provides. Ignored when EnableTLS is
false, the same as on DriverTLS. Unset is on.
true Optional: {}
provider ControlPlaneTLSProvider Provider issues the serving certificate. cert-manager Enum: [cert-manager OpenShift]
Optional: {}

ControlPlaneTLSProvider

Underlying type: string

ControlPlaneTLSProvider is who issues the control plane's serving certificate.

The two values are each product's own spelling rather than this group's PascalCase, because they are not this group's words: they are what internal/utils/tls.go already matches on and what the control-plane image reads out of SB_TLS_PROVIDER.

Validation: - Enum: [cert-manager OpenShift]

Appears in: - ControlPlaneTLS

Field Description
cert-manager ControlPlaneTLSCertManager issues through cert-manager, from the CA the
deployment's ClusterIssuer mints.
OpenShift ControlPlaneTLSOpenShift issues through the OpenShift service CA, which
signs from a Service annotation rather than from an object of its own.

CreatorReference

CreatorReference names the object that created a PersistentVolumeOps.

It exists because a cluster-scoped object cannot be owned by a namespaced one: Kubernetes treats such a reference as unresolvable and garbage-collects the dependent. Core Kubernetes has the same situation twice and answers it the same way: a PersistentVolume names its claim through spec.claimRef and a VolumeSnapshotContent names its snapshot through spec.volumeSnapshotRef, both with a UID, and that shape carries over here unchanged.

Appears in: - PersistentVolumeOpsSpec

Example:

kind: string
namespace: string
name: string
uid: UID
Field Description Default Validation
kind string Kind is the creating object's kind, which is StorageNodeOps for a drain. Required: {}
namespace string Namespace is where the creator lives. Required: {}
name string Name is the creator's object name. Required: {}
uid UID ⧉ UID is what makes the reference address one creator rather than one name:
a creator deleted and recreated under the same name must not inherit the
fan-out it did not issue, and cascade a delete over it.
Required: {}

DataRealignmentSettings

DataRealignmentSettings tunes the post-migration control-plane data realignment, which re-aligns the control plane's internal structures to where volumes now are and restores the fault-tolerance and node-affinity guarantees a move invalidated.

Whether it runs at all is StorageClusterSpec.DisableDataRealignment, a field of the spec rather than of this block: a toggle named for its subject repeats itself when the subject is also its parent.

Appears in: - VolumeMigrationSettings

Example:

interval: Duration
minMoves: integer
Field Description Default Validation
interval Duration ⧉ Interval is a floor on the spacing between realignment requests, and not
a ceiling on how long one takes: a realignment blocks every volume
migration for as long as the control plane needs, measured at tens of
minutes on a loaded cluster. The trigger annotation bypasses it. Defaults
to 10m.
Optional: {}
minMoves integer MinMoves is how many volume moves accumulate before a realignment is
requested. It defaults to 1, which makes migration and realignment
alternate, and is the field to raise in order to batch: a higher value
trades how promptly the structures are realigned for migration
throughput. The trigger annotation bypasses it.
Minimum: 1
Optional: {}

DeploymentImages

DeploymentImages is every image a deployment pins, in one block.

All three are software that runs on a storage node, which is what the slot names say and what an earlier spelling of the first one hid. They are together rather than each beside the object it configures because pinning images is one decision taken once: an air-gapped installation overrides all three against its own registry, and a reviewer reading the document has one place to check what this deployment will run. The expansion is what spends them on two different objects, since the three fields they land on are not all on the same kind.

The control plane's own image is not here. A ClusterDeploymentConfig creates a StorageCluster and its StorageNodes and neither creates nor adopts the ControlPlane, so a slot for it would be a field the expansion has nowhere to write. It is ControlPlane.spec.source.local.image, and the CSI driver's is SimplyblockDriver.spec.image.

Appears in: - ClusterDeploymentConfigSpec

Example:

nodeAgent:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
spdk:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
spdkProxy:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
Field Description Default Validation
nodeAgent ImageSpec NodeAgent is the image the storage-node DaemonSet runs: the agent the
control plane drives a worker through, and the two init containers that
configure the host before it starts. The expansion writes it to
StorageCluster.spec.storageNodes, which is where the retired
StorageNodeSet.spec.clusterImage went.
It is the same artifact the control plane itself runs, which is why
spec.storageNodes.image defaults to the ControlPlane singleton's: the agent
and the tasks that call it are one codebase, and version skew between them
is what breaks a node add.
It is spent only where the document creates the cluster. A document that
names an existing one in ClusterRef adds nodes to a DaemonSet that is
already running under an image the cluster states, and this slot is ignored
the same way Cluster is.
Optional: {}
spdk ImageSpec SPDK is the SPDK image, which the expansion writes onto every
StorageNode.spec.config it creates rather than onto the cluster: the field
is per node so that a later rollout can walk the fleet one machine at a
time, and a document states the fleet's starting point.
Optional: {}
spdkProxy ImageSpec SPDKProxy is the SPDK proxy image, written per node for the reason SPDK is. Optional: {}

DeviceCapacity

DeviceCapacity is how big the device is. It carries no used size and no sample time, because a physical device cannot be resized: the number is a property of the hardware, it is written when the device is discovered, and nothing rewrites it. A device reporting a different size is a different device, which arrives as its own object.

What the device holds is the number that moves, and it is served from metrics.simplyblock.io as StorageDeviceMetrics rather than published here. A reading that changes continuously is not desired state, and keeping it in a status would write etcd on every sample and wake every watcher of the kind for it.

Appears in: - StorageDeviceStatus

Example:

totalBytes: integer
Field Description Default Validation
totalBytes integer TotalBytes is the device's usable size, as the control plane reports it
with the device.
Minimum: 0
Optional: {}

DeviceFilter

DeviceFilter narrows what a discovery run reports as a candidate device. It is an input to discovery and never appears in the document discovery writes: a ClusterDeploymentConfig carries the explicit list the filter produced, not the rule that produced it.

Every member narrows the devices of the class a run scans. Neither choosing that class nor waiving an availability condition is a member: both are statements about the run, spec.discover.enableLogicalBlockDevices and spec.discover.enablePartitionedDevices, because the first decides which kind of cluster the draft describes and the second widens what is reported. The filters come in two sets, one per class, and the rules on DiscoverSpec reject the set belonging to the class the run is not scanning.

Appears in: - DiscoverSpec

Example:

pcieAllowList:
  - string
pcieDenyList:
  - string
pcieModel: string
blockAllowList:
  - '^/dev/[a-zA-Z0-9._/-]+$'
blockDenyList:
  - '^/dev/[a-zA-Z0-9._/-]+$'
driveSizeRange: string
Field Description Default Validation
pcieAllowList string array PcieAllowList restricts candidates to these PCI addresses. This and the two
PCI filters below narrow the NVMe class alone, because a logical block
device has no PCI address to match, so setting any of them on a run that
scans the block class is rejected by the rule on this type.
Optional: {}
pcieDenyList string array PcieDenyList excludes these PCI addresses. On a fleet that is uniform
about which slot holds the boot device, this is what keeps that device out
of every group of every draft.
Optional: {}
pcieModel string PcieModel restricts candidates to devices whose PCI model string matches. Optional: {}
blockAllowList string array BlockAllowList restricts candidates to these device paths ("/dev/sdb").
This and BlockDenyList are the block class's half of the filter, and they
require EnableLogicalBlockDevices for the same reason the PCI filters
forbid it.
items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$
Optional: {}
blockDenyList string array BlockDenyList excludes these device paths. On a fleet that boots from
/dev/sda, this is the one entry that keeps the root disk out of every group
of every draft.
items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$
Optional: {}
driveSizeRange string DriveSizeRange restricts candidates by size ("100G-2T"). Unlike the
per-class filters, it applies to whichever class the run is scanning.
Optional: {}

DeviceHardware

DeviceHardware identifies the part. These are what somebody walking into a datacenter with a failed drive needs, and none of them is recoverable from the control plane's device id alone. Every field is optional, because which ones a device has depends on how it is attached.

The design's devicePath is absent: the control plane reports no host path for a device, only the NVMe controller it hangs off, so the field would never be populated. NVMeController carries what is actually reported.

Appears in: - StorageDeviceStatus

Example:

pciAddress: string
serialNumber: string
model: string
nvmeController: string
Field Description Default Validation
pciAddress string PCIAddress is the device's address on the host ("0000:5e:00.0"), where it
has one. A logical block device may not: the address can belong to the
controller it hangs off rather than to the device.
Optional: {}
serialNumber string SerialNumber is what is printed on the drive. Optional: {}
model string Model is the manufacturer's model string. Optional: {}
nvmeController string NVMeController is the controller the device is served through, in the
control plane's spelling.
Optional: {}

DeviceSelection

DeviceSelection is the explicit list of storage devices a group's workers hand to simplyblock. It carries no filter of any kind: a document whose meaning depends on what the hardware turns out to be is not a document a reviewer can approve, so filtering happens in the discovery run that produces the list and what lands here is the result. It expands to the matching fields of StorageNode.spec.config, which carry the same meanings.

One member and not both. A cluster is built out of one class of backend storage, so a group hands over NVMe devices or logical block devices, and the rule below is the half of that a single group can be checked against. That every group of the document agrees is the spec's rule.

Appears in: - NodeGroup

Example:

nvme:
  - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
  - '^/dev/[a-zA-Z0-9._/-]+$'
Field Description Default Validation
nvme string array NVMe names NVMe devices by PCI address ("0000:5e:00.0"). MaxItems: 128
items:MaxLength: 32
items:Pattern: ^[0-9a-fA-F]\{4\}:[0-9a-fA-F]\{2\}:[0-9a-fA-F]\{2\}\.[0-9a-fA-F]$
Optional: {}
block string array Block names logical block devices by path ("/dev/sdb"). It expands into the
same config.deviceNames as NVMe, which takes a PCI address and a device
path in one list. It is the alternative to NVMe rather than a companion of
it: the two classes are not mixed within a cluster.
MaxItems: 128
items:MaxLength: 255
items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$
Optional: {}

DiscoverSpec

DiscoverSpec parameterizes the Discover action.

Which workers a run inspects is stated one of two ways and never both: by name in Workers, or by label in NodeSelector. They are exclusive rather than intersected because the intersection of a name list and a label selector is a question nobody asks deliberately, and reading one as narrowing the other would make a run inspect fewer machines than either field says.

The device filters come in two sets, one per device class, and a run scans one class. The two class rules reject the set belonging to the class this run is not scanning, because a filter that will never be applied is one an administrator reads as having narrowed a draft that was never narrowed.

Appears in: - OperatorOpsSpec

Example:

configName: string
workers:
  - string
nodeSelector:
  string: string
tolerations:
  - Toleration
enableControlPlaneNodes: boolean
enableLogicalBlockDevices: boolean
enablePartitionedDevices: boolean
forceJournalDevice: boolean
deviceFilter:
  pcieAllowList:
    - string
  pcieDenyList:
    - string
  pcieModel: string
  blockAllowList:
    - '^/dev/[a-zA-Z0-9._/-]+$'
  blockDenyList:
    - '^/dev/[a-zA-Z0-9._/-]+$'
  driveSizeRange: string
clusterRef: string
Field Description Default Validation
configName string ConfigName is the ClusterDeploymentConfig to write. Absent generates one
from the run's timestamp, so that a second discovery never overwrites the
first, which may have been reviewed and corrected.
Optional: {}
workers string array Workers are the workers to inspect, by node name.
It is the answer to inspecting two named machines, which a label selector
can only express by labeling them first: a selector's entries are ANDed,
so two hostnames in one selector match nothing at all.
A named worker that does not exist, or that is declined for one of the
reasons any worker is declined, is reported by the same event the selector
path reports it by. Naming a worker is a statement about which machines to
consider, not a claim that each of them will be used.
MaxItems: 128
items:MaxLength: 253
Optional: {}
nodeSelector object (keys:string, values:string) NodeSelector restricts which workers are inspected. Empty inspects every
schedulable worker, and it is exclusive with Workers.
Optional: {}
tolerations Toleration ⧉ array Tolerations are what the probe pods tolerate, and what the draft states
for the storage nodes it proposes.
A probe is pinned to its worker with spec.nodeName rather than scheduled
onto it, which bypasses the scheduler and not the taints: a NoSchedule
taint still keeps the pod off, and a NoExecute taint evicts one that
landed. A fleet that dedicates machines to storage taints them, so a run
against one that tolerates nothing inspects nothing.
They reach the draft as well, because the taints a run was allowed to
probe through are the taints the cluster it proposes has to live with.
Stating them in one place is what keeps a reviewer from approving a
document whose DaemonSet schedules nowhere.
MaxItems: 32
Optional: {}
enableControlPlaneNodes boolean EnableControlPlaneNodes lets the run consider machines that run the API
server and etcd.
It is off by default because a storage node is a data path, and putting one
on an etcd host is a placement almost nobody intends. The approval gate is a
poor place to catch it: a fifty-worker draft is not a document anybody reads
closely enough to spot three control-plane nodes in it. A combined three-node
or single-node deployment is the case that wants it, and those are set up
deliberately.
There is no field beside it for infrastructure nodes, because those are used
without asking: an OpenShift infra node is the tier a cluster's own
infrastructure runs on, and simplyblock storage is infrastructure. A fleet
with disks in its infra nodes meant those disks to be the storage, so a draft
proposes them ahead of the workers rather than leaving them out.
Optional: {}
enableLogicalBlockDevices boolean EnableLogicalBlockDevices scans a worker's available logical block devices
instead of its available NVMe devices. It selects the class rather than
adding one, because the draft a run writes describes one cluster and a
cluster is built out of one class. Unset scans NVMe, so that upgrading to
26.4 does not change what a discovery run reports.
It is a statement about the run rather than a member of DeviceFilter,
because it does not narrow the devices reported: it decides which class of
them is looked at, which filters in DeviceFilter apply, and what
ForceJournalDevice resolves, since the two classes lay out a journal
differently.
Optional: {}
enablePartitionedDevices boolean EnablePartitionedDevices reports devices carrying a partition table
alongside the available ones, for the administrator who knows the table is
stale and intends to hand the device over anyway. It is the only one of the
three availability conditions that can be waived: a mounted or otherwise
busy device is never reported, because simplyblock taking it would corrupt
whatever is using it.
It is a statement about the run rather than a member of DeviceFilter for
the reason EnableLogicalBlockDevices is: it waives an availability
condition for whichever class is scanned, and so widens what is reported,
where every member of the filter narrows it.
Optional: {}
forceJournalDevice boolean ForceJournalDevice makes the run dedicate a journal device even where the
fleet's disks do not say which one.
A run proposes a dedicated journal device when every worker hands over one
disk smaller than its others, because a fleet built that way was built that
way on purpose. Where several disks share the smallest size, nothing says
which to take, and taking one spends a whole disk the fleet did not set
aside: on a worker with ten 10 TB disks that is 10 TB, silently. So the run
does not guess.
What it does instead depends on the class. An NVMe cluster with no
dedicated journal device carves a journal partition out of every device, so
the run leaves the field unset, says so, and every disk stays storage. A
logical block-device cluster has no such layout — the control plane refuses
the partitioned journal for the class — so the run fails rather than
writing a document that creates the cluster, formats its drives, and then
fails every node_add. Setting this is what asks for one of the equal disks
to be taken anyway, and the draft says the disk was taken rather than
offered.
It does not force the cases that are impossible rather than ambiguous. A
worker handing over a single disk is refused with it or without it, because
dedicating that disk leaves the worker nothing to store on, and so is a
worker whose disks report no size. The tie it does resolve is broken by
address, ascending, so two runs over one unchanged fleet propose the same
document.
Optional: {}
deviceFilter DeviceFilter DeviceFilter narrows which of an inspected worker's devices reach the
draft. Empty reports every device the worker advertises, including the one
it boots from, which is what the approval gate then has to catch.
Optional: {}
clusterRef string ClusterRef names an existing StorageCluster the draft grows rather than
creates. It is copied to the draft's own clusterRef, so that re-running
discovery after an expansion produces a growth document naming the same
cluster.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Optional: {}

DrainStatus

DrainStatus is the drain's progress over the volumes on the node being removed. Neither field takes omitempty: zero is meaningful for both, and a field that disappears at zero makes "nothing to move" and "not yet counted" the same wire value.

Appears in: - StorageNodeOpsStatus

Example:

volumesTotal: integer
volumesMigrated: integer
Field Description Default Validation
volumesTotal integer VolumesTotal is the number of PV-managed volumes the drain has to move,
written once at the end of Validating and not modified afterward.
Minimum: 0
volumesMigrated integer VolumesMigrated is how many of them have completed. Minimum: 0

DriverPNFS

DriverPNFS configures pNFS support, which makes the node plugin an NFS metadata server as well as an NVMe-oF initiator: on the host an export binds to, it makes a filesystem on the namespace, mounts it, and publishes it through the host's nfsd.

Turning it on is not sufficient. The host needs nfs-utils and a running nfsd, and a client host needs blkmapd, neither of which a pod can install. A host missing either fails visibly in the export's Assembling phase.

Appears in: - SimplyblockDriverSpec

Example:

enablePNFS: boolean
Field Description Default Validation
enablePNFS boolean EnablePNFS gives the node plugin the four host directories an export is
assembled through. It is gated because a plugin not serving exports has
no use for them.
false Optional: {}

DriverTLS

DriverTLS configures whether this deployment's two plugins reach the control plane over TLS. Unset (every field at its zero value) is a plaintext data path, which is what every deployment ran as before this field existed.

The client-certificate Secret each plugin mounts is not named here. It is <object name>-csi-controller-client-tls and <object name>-csi-node-client-tls, derived from the object's name like every other object this deployment creates, and a field naming them again would be a second place for the two to disagree.

Appears in: - SimplyblockDriverSpec

Example:

enableTLS: boolean
enableMutualTLS: boolean
provider: DriverTLSProvider
Field Description Default Validation
enableTLS boolean EnableTLS turns on TLS between both plugins and the control plane. false Optional: {}
enableMutualTLS boolean EnableMutualTLS additionally requires each plugin to present a client
certificate, rather than dialing the control plane anonymously over
the encrypted connection EnableTLS alone provides. Ignored when
EnableTLS is false, the same as the Helm value it replaces.
false Optional: {}
provider DriverTLSProvider Provider selects where the CA bundle (and, with EnableMutualTLS, the
client certificate) comes from. Required reading whenever EnableTLS is
true: the two providers mount a differently shaped volume, and neither
shape can be inferred from anything else on this object.
cert-manager Enum: [OpenShift cert-manager]
Optional: {}

DriverTLSProvider

Underlying type: string

DriverTLSProvider is where the TLS certificate on this connection comes from. The values are not this group's to spell: OpenShift and cert-manager are the two products, and the operator's own internal/utils package already carries these exact strings for the control plane's own SB_TLS_PROVIDER, so a driver and a control plane in the same namespace agree on the same word without a translation table between them.

Validation: - Enum: [OpenShift cert-manager]

Appears in: - DriverTLS

Field Description
OpenShift DriverTLSProviderOpenShift is OpenShift's service-ca operator: a
ConfigMap carrying the cluster CA, and a Secret an administrator
provisions for each plugin's client certificate.
cert-manager DriverTLSProviderCertManager is cert-manager: a ClusterIssuer already
installed by this chart mints a Certificate per plugin, and the Secret
it writes carries the CA bundle alongside the client keypair.

FailureDomainIndex

FailureDomainIndex is one failure-domain label and the control plane's index for it.

Appears in: - StorageClusterStatus

Example:

name: string
index: integer
Field Description Default Validation
name string Name is the failure-domain label, as StorageNode.spec.config.failureDomain
spells it ("rack-b").
MaxLength: 63
MinLength: 1
Required: {}
index integer Index is the integer sent to the control plane for every node in the
domain.
Minimum: 0
Required: {}

FoundationDBSpec

FoundationDBSpec is the sizing of the FoundationDB the operator installs.

Appears in: - LocalControlPlane

Example:

replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
  string: string
Field Description Default Validation
replicas integer Replicas is the number of coordinators. Three is the smallest count that
survives one loss, which is why it is the default.
3 Minimum: 1
Optional: {}
storageClassName string StorageClassName is the class the coordinators' volumes are provisioned
from. It cannot be a class this operator provides, because the control
plane has to exist before any simplyblock volume can.
Optional: {}
resources ResourceRequirements ⧉ Resources sets requests and limits for the coordinator pods. Optional: {}
nodeSelector object (keys:string, values:string) NodeSelector pins the database's own processes, and nothing else the
control plane runs. Unset leaves them wherever the control plane as a
whole is placed.
It is stated apart from that placement because the database is the part
whose host matters to something else: adding a storage node reboots its
worker, and a worker running a FoundationDB process is added on its own
however many nodes the cluster's provisioning budget allows at once.
Confining the database to a few workers is what leaves the rest of the
fleet free to be added together.
It has to select at least as many workers as
spec.source.local.foundationDB.replicas, because the worker is the
database's fault domain. The anti-affinity between processes of one class
is a preference rather than a requirement, so a narrower selector places
the coordinators anyway, several to a worker, where one worker's loss
takes more of them than the redundancy mode was configured to survive.
Optional: {}

HostOSFamily

Underlying type: string

HostOSFamily is the packaging tradition a Linux distribution belongs to.

It is the coarse half of what a host OS is, and the half most decisions are actually about: what differs between Ubuntu and Debian is rarely what a storage node needs, and what differs between Ubuntu and Rocky always is. There is no member for a host with no package manager: Talos and Flatcar are not a family with no name, they are machines where the question does not arise, and a document describing one leaves the family unstated.

Validation: - Enum: [Debian RedHat SUSE Alpine Arch]

Appears in: - HostOSSpec

Field Description
Debian
RedHat
SUSE
Alpine
Arch

HostOSSpec

HostOSSpec is the operating system a deployment's workers run.

It is a fact about the machines rather than about Kubernetes, which is why it is stated here and not derived from spec.environment: a fleet on OpenShift runs Red Hat Enterprise Linux CoreOS, and a fleet on K3s runs whatever its administrator installed. A discovery run fills it in from what the probes read, and fills it in only when every worker agrees, so a document that states one is a document whose fleet is uniform.

Appears in: - ClusterDeploymentConfigSpec

Example:

distro: '^[a-z0-9][a-z0-9._-]*$'
family: HostOSFamily
Field Description Default Validation
distro string Distro is the distribution's os-release ID, lowercase and verbatim:
ubuntu, rocky, rhel, talos. It is what the expansion reads.
MaxLength: 63
Pattern: ^[a-z0-9][a-z0-9._-]*$
Optional: {}
family HostOSFamily Family is the packaging tradition Distro belongs to. A discovery run
concludes it from the distribution itself, or from the distributions its
os-release says it is built on, which is what places a derivative this
product has never heard of. It is left unstated for a host with no
package manager.
Enum: [Debian RedHat SUSE Alpine Arch]
Optional: {}

ImageSpec

ImageSpec is one container image and when to pull it, which is the pair every image in this product is stated as.

Both members are optional so that each can be stated without the other: an air-gapped deployment overrides the image and keeps the policy, and a development one keeps the image and pins the policy to IfNotPresent so a tag rebuilt in place is not picked up mid-deployment.

Appears in: - DeploymentImages

Example:

image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
Field Description Default Validation
image string Image is the repository and tag, optionally digest-pinned. An empty value
is not written downstream, so the field it would fill keeps its own
default rather than being overridden with nothing.
The registry set is the one every image field in this API is held to,
which is what keeps a reviewed document from naming a build nobody
published.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
imagePullPolicy PullPolicy ⧉ ImagePullPolicy is when that image is pulled. It defaults to Always,
because every image this product ships by default is a moving tag and a
node brought up after a release otherwise runs what its kubelet held.
Always Enum: [Always Never IfNotPresent]
Optional: {}

JournalManagerSpec

JournalManagerSpec tunes the journal managers on one storage node.

Appears in: - NodeGroup - StorageNodeConfig

Example:

count: integer
percentPerDevice: integer
Field Description Default Validation
count integer Count is the number of journal managers to configure. The control plane
requires at least 3.
Minimum: 3
Optional: {}
percentPerDevice integer PercentPerDevice is the share of each device given to the journal. Maximum: 100
Minimum: 1
Optional: {}

KMSSpec

KMSSpec selects where the cluster stores volume encryption keys. It is a block with one member per provider so that the providers are siblings, which is what makes choosing between them expressible.

Appears in: - ClusterTemplate - StorageClusterSpec

Example:

vault:
  endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
Field Description Default Validation
vault VaultKMS Vault stores keys in HashiCorp Vault. Optional: {}

KubernetesEnvironment

Underlying type: string

KubernetesEnvironment is the distribution a deployment targets. The values are the distributions' own names.

Validation: - Enum: [Vanilla OpenShift Rancher K3s Talos]

Appears in: - ClusterDeploymentConfigSpec - OperatorOpsStatus

Field Description
Vanilla
OpenShift
Rancher
K3s
Talos

LocalControlPlane

LocalControlPlane is a control plane this cluster hosts, installed and owned by the operator. Its objects carry a controller reference to the ControlPlane, so the ownership spine starts at a real edge rather than at a Helm release.

Appears in: - ControlPlaneSource

Example:

image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
foundationDB:
  replicas: integer
  storageClassName: string
  resources: ResourceRequirements
  nodeSelector:
    string: string
replicas: integer
resources: ResourceRequirements
tolerations:
  - Toleration
nodeSelector:
  string: string
tls:
  enableTLS: boolean
  enableMutualTLS: boolean
  provider: ControlPlaneTLSProvider
adminTokenSecretRef: LocalObjectReference
observability:
  enableMonitoring: boolean
  secretRef: LocalObjectReference
Field Description Default Validation
image string Image is the management API and control-plane image.
Must reference one of the trusted registries (quay.io/simplyblock-io,
docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning
(@sha256:...) is recommended.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Required: {}
imagePullPolicy PullPolicy ⧉ ImagePullPolicy controls when that image is pulled. IfNotPresent Enum: [Always Never IfNotPresent]
Optional: {}
foundationDB FoundationDBSpec FoundationDB sizes the FoundationDB the management API stores its state in. Optional: {}
replicas integer Replicas is the number of management API instances. Two is what the chart
ships and what the phases assume: a single instance makes Degraded
unreachable for this component and every restart an outage.
2 Minimum: 1
Optional: {}
resources ResourceRequirements ⧉ Resources sets requests and limits for the management API pods. Optional: {}
tolerations Toleration ⧉ array Tolerations are applied to every pod the operator installs for the control
plane.
Optional: {}
nodeSelector object (keys:string, values:string) NodeSelector pins every pod the operator installs for the control plane.
It is a selector rather than an affinity term because that is what the
chart it replaces took, and a deployment migrating off the chart has the
value already written down.
Optional: {}
tls ControlPlaneTLS TLS is how this control plane serves and what it asks of its callers.
The block is defaulted to its own zero value rather than left absent, so
that the field defaults inside it are applied to an object that does not
mention TLS at all.
{ } Optional: {}
adminTokenSecretRef LocalObjectReference ⧉ AdminTokenSecretRef names a Secret in this namespace holding a static
admin bearer token this control plane accepts, under the token key, in
addition to this deployment's own Kubernetes identity
(SB_K8S_ADMIN_SERVICE_ACCOUNTS). It is what lets a cluster this control
plane manages remotely (spec.source.managed there,
ManagedControlPlane.CredentialsSecretRef naming the same value)
authenticate a CreateCluster call, since a Kubernetes TokenReview can
never cross a cluster boundary.
The Secret is projected into the management API container's environment
with secretKeyRef, so this operator never itself reads the plaintext.
Absent grants no credential beyond the operator's own service account.
Optional: {}
observability ControlPlaneObservability Observability connects this control plane to the monitoring stack
installed beside it: Graylog, Grafana, and OpenSearch. Absent leaves
monitoring off.
Optional: {}

ManagedControlPlane

ManagedControlPlane is a control plane somewhere else, which this cluster's storage is managed by rather than hosting. The operator installs nothing and owns nothing here: it resolves the endpoint, probes it, and reports.

The word is about what manages the storage clusters rather than about who runs the operator. A deployment in this mode registers its clusters with a control plane it does not host, so the fleet is administered from there.

Appears in: - ControlPlaneSource

Example:

endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
credentialsSecretRef: LocalObjectReference
caBundleSecretRef: LocalObjectReference
storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
Field Description Default Validation
endpoint string Endpoint is the management API's base URL. It is validated against the
same outbound-URL guard every other outbound endpoint in this group uses,
so a loopback or link-local address is rejected.
Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$
Required: {}
credentialsSecretRef LocalObjectReference ⧉ CredentialsSecretRef names a Secret in this namespace holding the bearer
token the operator authenticates with. It is a reference rather than a
field because a token in a spec is a token in every kubectl get -o yaml.
Absent means the endpoint is reached without one, which is the in-cluster
case: a control plane the Helm chart installed answers on a ClusterIP
Service in this namespace and does not require a token for the readiness
read. Naming a Secret that does not exist stays an error, because naming
one is a statement that the control plane needs it.
Optional: {}
caBundleSecretRef LocalObjectReference ⧉ CABundleSecretRef names a Secret holding the CA certificate the endpoint
is verified against. Absent means the system trust store.
Optional: {}
storageNodeImage string StorageNodeImage is the storage-node image a StorageCluster on this
Kubernetes cluster defaults to when its own spec.storageNodes.image is
unset. A local control plane's own spec.source.local.image doubles as
this default (StorageNodeWorkloadReconciler.image), because a
self-hosted deployment's control plane and its storage nodes are one
release. A managed one is a different Kubernetes cluster's install and
says nothing about what this cluster's storage nodes should run, so
there is no equivalent to fall back to without this field -- every
StorageCluster on a managed deployment must get an image from here or
from its own spec.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}

MetricsBackend

Underlying type: string

MetricsBackend selects where the rebalancer reads a node's I/O load from. The values are PascalCase, as every enum this group defines is; v1alpha1 spelled them lowercase and the conversion maps between the two.

Validation: - Enum: [ControlPlane Prometheus Uniform]

Appears in: - VolumeAutoPlacementSettings

Field Description
ControlPlane
Prometheus
Uniform MetricsBackendUniform reports IOPS=1 for every node, which disables
IOPS-based scoring while leaving capacity and volume-count balancing
active.

MigrateSpec

MigrateSpec parameterizes the Migrate action and is ignored by the others.

A migration is not a removal followed by an add: the node keeps its backend UUID, its partitions, and its logical-volume assignments, and what changes is the machine the SPDK process runs on. No PersistentVolumeOps is created.

Appears in: - StorageNodeOpsSpec

Example:

targetWorkerNode: string
newSsdPcie:
  - string
Field Description Default Validation
targetWorkerNode string TargetWorkerNode is the Kubernetes worker the node is relocated onto. Required: {}
newSsdPcie string array NewSsdPcie lists additional NVMe PCI addresses to bind on the target host,
passed through to the control-plane restart as new_ssd_pcie and merged into
the node's effective allow list so they survive a later rebuild.
Optional: {}

MigrateVolumeSpec

MigrateVolumeSpec parameterizes the Migrate action.

Appears in: - PersistentVolumeOpsSpec

Example:

targetNodeRef:
  namespace: string
  name: string
Field Description Default Validation
targetNodeRef StorageNodeReference TargetNodeRef locates the StorageNode to move the volume's backing
logical volume to. It names the Kubernetes object rather than the backend
UUID, so that a migration can be written by hand without looking one up;
the controller resolves the UUID from the node's status. The node's
cluster must be the volume's, which the webhook checks rather than the
type, because that is a fact about two other objects.
The marker sits on the reference rather than on its fields, so the pair
is immutable together and a target cannot be half-changed into a name in
one cluster and a namespace in another.
Required: {}

MigrationConnection

MigrationConnection is one NVMe-oF path the migration published on the target.

The connect parameters travel with the address because the path is connected on a consuming host rather than here, and what is recorded has to be the connect that will actually be made: the host attaches every path with the same controller-loss timeout the CSI driver uses, which is not the hour the control plane answers with, and a record of the control plane's answer would describe a connect nobody performs.

Appears in: - MigrationStatus

Example:

nqn: string
address: string
port: integer
transport: string
nrIOQueues: integer
reconnectDelaySeconds: integer
ctrlLossTimeoutSeconds: integer
fastIOFailTimeoutSeconds: integer
keepAliveTimeoutSeconds: integer
Field Description Default Validation
nqn string NQN is the subsystem the target answers this path for. Optional: {}
address string Address and Port are where it answers. Optional: {}
port integer Optional: {}
transport string Transport is the fabric, which is TCP. Optional: {}
nrIOQueues integer Optional: {}
reconnectDelaySeconds integer Optional: {}
ctrlLossTimeoutSeconds integer CtrlLossTimeoutSeconds and FastIOFailTimeoutSeconds are pointers because
zero is a choice ("fail I/O immediately") rather than a missing value.
Optional: {}
fastIOFailTimeoutSeconds integer Optional: {}
keepAliveTimeoutSeconds integer Optional: {}

MigrationStatus

MigrationStatus is everything about the migration rather than about the operation. It is durable working state: a controller that restarts mid-copy reads it to find the backend migration it started and the Jobs it has to clean up.

Appears in: - PersistentVolumeOpsStatus

Example:

migrationUUID: string
clusterUUID: string
poolUUID: string
volumeUUID: string
subsystemNQN: string
sourceNodeUUID: string
targetNodeUUID: string
continuedAt: Time
memberCount: integer
connections:
  - nqn: string
    address: string
    port: integer
    transport: string
    nrIOQueues: integer
    reconnectDelaySeconds: integer
    ctrlLossTimeoutSeconds: integer
    fastIOFailTimeoutSeconds: integer
    keepAliveTimeoutSeconds: integer
validationJobs:
  - namespace: string
    name: string
    node: string
    succeeded: boolean
Field Description Default Validation
migrationUUID string MigrationUUID is the control plane's identifier for the copy. Optional: {}
clusterUUID string ClusterUUID, PoolUUID, and VolumeUUID are the three parts of the volume's
CSI volume handle, recorded so that later steps address the backend
without re-reading the PersistentVolume, and so that a failed operation
says which volume it was working on after the volume is gone.
Optional: {}
poolUUID string Optional: {}
volumeUUID string Optional: {}
subsystemNQN string SubsystemNQN is the volume's NVMe-oF subsystem, which is what the
migration is addressed by: the control plane migrates a subsystem rather
than one volume inside it.
Optional: {}
sourceNodeUUID string SourceNodeUUID is where the volume was before the move, recorded so that
a failure says what it was and not only what it was going to be.
Optional: {}
targetNodeUUID string TargetNodeUUID is the backend identifier resolved from
spec.migrate.targetNodeRef, recorded so the later steps address the
target without resolving the node object again.
Optional: {}
continuedAt Time ⧉ ContinuedAt is when this operation asked the control plane to start the
copy, written before the call rather than after it.
The order is what makes the request at-most-once, and that is the point.
A repeated continue is the shape that has lost writes here: a read
timeout on a call that had in fact committed made the operator retry a
transfer, and the retry copied nothing while the source was unfrozen. So
a recorded continue is never issued again, and an operation that crashed
between this write and the call waits out its step's deadline instead —
a rare stall, against a silent data loss.
Optional: {}
memberCount integer MemberCount is how many volumes the migrated NVMe-oF subsystem holds, as
the control plane reports it. More than one member means the sibling
volumes move along with the named one, so the count is both the
operation's blast radius and the term the copy's deadline scales by.
Minimum: 0
Optional: {}
connections MigrationConnection array Connections are the paths the migration published on the target.
Verifying confirms none of them is left connected.
Optional: {}
validationJobs ValidationJob array ValidationJobs are the Jobs started to check those paths. Optional: {}

NFSExport

NFSExport is one pNFS export: a volume, the host serving it, and the address clients mount.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: NFSExport
metadata:
  name: string
spec:
  volumeRef: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
  exportPath: '^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$'
  encrypted: boolean
  sizeBytes: integer
status:
  phase: NFSExportPhase
  phaseDeadline: Time
  mdsNodeName: string
  mdsNodeIP: string
  serviceAddress: string
  allowedClients:
    - string
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string NFSExport
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec NFSExportSpec
status NFSExportStatus

NFSExportPhase

Underlying type: string

NFSExportPhase is the lifecycle position of an export.

Validation: - Enum: [Pending Assembling Ready Degraded]

Appears in: - NFSExportStatus

Field Description
Pending Pending is an export with no host bound yet: a wait, not a failure.
Assembling Assembling is the bound host attaching, formatting, mounting, publishing.
Ready Ready is an export a client can mount.
Degraded Degraded is an export the operator will not act further on. Reached by
declining to act: a stalled export is recoverable, a double-mounted one
is not.

NFSExportSpec

NFSExportSpec is the desired state. The bound host is deliberately not here: it is an observed binding the operator owns, so it lives in status where a user edit cannot race it.

Appears in: - NFSExport

Example:

volumeRef: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
exportPath: '^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$'
encrypted: boolean
sizeBytes: integer
Field Description Default Validation
volumeRef string VolumeRef is the CSI volume handle of the volume this export serves, in
the ordinary {clusterID}:{poolID}:{lvolID} form. It names no Kubernetes
object, so it is validated by pattern rather than resolved by a webhook.
It is the whole identity of the export. The backing namespace UUID and
the NFS fsid are both the volume's own id, so they are read from here
rather than stored beside it, where they could disagree with it.
Deliberately not a form of its own. A pNFS volume is an lvol with an
export in front of it, so its claim keeps the lvol's handle and every
other CSI call -- snapshot, clone, expand -- addresses it without having
to know an export is there.
Pattern: ^[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}:[^:]+:[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}$
Required: {}
exportPath string ExportPath is the server-side mount point. It carries the namespace,
because a PVC name is unique only within one.
Pattern: ^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$
Required: {}
encrypted boolean Encrypted says the control plane stacks a crypto bdev under this volume,
which makes an unwritten block read as pseudo-random plaintext rather
than as zeros. Assembly needs it: without it an empty encrypted volume
is mistaken for an occupied one, never formatted, and never mounts.
No omitempty: the field is immutable once set (below), and the CRD
enforces that by requiring it stay present once it is. The CSI
controller's unstructured Create always writes it explicitly, so an
unencrypted export's record already carries encrypted: false in
etcd; a typed client's own writes have to carry it too; omitempty
drops a false value from the JSON entirely, which reads to the CRD as
the field being removed and every subsequent write from this type --
starting with the reconciler's own finalizer add -- is then refused,
permanently, before the export can even begin assembling.
Optional: {}
sizeBytes integer SizeBytes is the capacity the backing volume was last grown to.
It is not a request: the control plane has already resized the volume by
the time this is written. It is here so that growing one bumps the
record's generation, which is what tells the operator to re-assemble and
run xfs_growfs on the host -- the one machine that can, and the one no
CSI call ever reaches.
Optional: {}

NFSExportStatus

NFSExportStatus is what the export currently is.

Appears in: - NFSExport

Example:

phase: NFSExportPhase
phaseDeadline: Time
mdsNodeName: string
mdsNodeIP: string
serviceAddress: string
allowedClients:
  - string
message: string
observedGeneration: integer
Field Description Default Validation
phase NFSExportPhase Phase is the export's lifecycle position. Enum: [Pending Assembling Ready Degraded]
Optional: {}
phaseDeadline Time ⧉ PhaseDeadline is when the current phase must be given up on. Persisted
because an operator restarted mid-assembly would otherwise grant a fresh
deadline on every pass and never time out.
Optional: {}
mdsNodeName string MDSNodeName is the Kubernetes node serving this export, which is the one
running the csi-node pod that assembled it. It is not a StorageNode: an
MDS reaches the volume over NVMe-oF exactly as a client does, so it need
not hold any storage itself.
The serialization point for one-MDS-per-export: no second host is a
candidate until this field is rewritten.
Optional: {}
mdsNodeIP string MDSNodeIP is the bound MDS host's own address. Not what a client mounts:
it is what the export's Service's EndpointSlice points at, so a client's
mount address (ServiceAddress) does not have to change when this does.
Optional: {}
serviceAddress string ServiceAddress is the ClusterIP of the Service fronting this export,
which is the address a client mounts. It outlives any one MDSNodeIP: the
operator repoints the Service's EndpointSlice at whichever host is bound
rather than changing this value, so a client's mount survives the export
moving to a different host (design-pnfs-rwx.md §13.3).
Optional: {}
allowedClients string array AllowedClients is what goes into the exports(5) entry: the internal
address of every node that could run a pod using this volume.
Optional: {}
message string Message is a human-readable note on the current phase. Optional: {}
observedGeneration integer ObservedGeneration is the spec generation this status was computed from. Optional: {}

NodeGroup

NodeGroup is a set of workers that share one configuration, which is what makes ten identical machines one entry rather than ten.

Appears in: - NodeSet

Example:

name: string
workers:
  - string
mgmtInterface: string
dataInterfaces:
  - string
devices:
  nvme:
    - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
  block:
    - '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
  count: integer
  percentPerDevice: integer
Field Description Default Validation
name string Name identifies the group within its node set, for a reader and for the
events a validation failure emits.
MaxLength: 253
Required: {}
workers string array Workers are the Kubernetes worker hostnames in this group. MaxItems: 200
MinItems: 1
Required: {}
items:MaxLength: 253
mgmtInterface string MgmtInterface is the management network interface the storage nodes bind. MaxLength: 63
Optional: {}
dataInterfaces string array DataInterfaces are the data-plane network interfaces. MaxItems: 32
items:MaxLength: 63
Optional: {}
devices DeviceSelection Devices selects the storage devices every worker in the group uses. Optional: {}
failureDomain string FailureDomain is the label of the fault group every worker in this group
belongs to ("rack-b"), which is usually the name of the rack, zone, or
power feed they share. Discovery seeds it from topology.kubernetes.io/zone
and leaves it unset where the Kubernetes API carries no topology, which
holds provisioning with a clear reason rather than guessing. It expands
into StorageNode.spec.config.failureDomain, whose shape it shares.
MaxLength: 63
Pattern: ^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$
Optional: {}
spdkSystemMemory string SpdkSystemMemory is the memory the control plane starts SPDK with on these
nodes.
MaxLength: 32
Pattern: ^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$
Optional: {}
reservedSystemCPU string ReservedSystemCPU is the CPU set held back from SPDK for the system on
these nodes, as a core list such as 0,1 or 0-3.
It is a group's rather than the cluster's because it names core ids, and a
group is what a document calls the workers that share their hardware: 0,1
on a sixteen-core worker and 0,1 on a ninety-six-core worker are different
fractions of the machine. It expands into
StorageNode.spec.config.reservedSystemCPU, whose shape it shares, and a
group that states none leaves the cluster's fleet-wide value to decide.
On OpenShift it reaches the kubelet through a KubeletConfig for the
machine config pool, which is the cluster's, so groups that disagree there
are writing over one another's pool configuration.
MaxLength: 63
Pattern: ^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$
Optional: {}
journalManager JournalManagerSpec JournalManager tunes the journal managers on these nodes. Optional: {}

NodeLatencyMetrics

NodeLatencyMetrics is the fio-measured 4K NVMe-oF write latency of one backend storage node, which is the denominator of the rebalancer's deviation signal.

It is a node's reading and it lives on the node. The retired StorageNodeSet collected one entry per node in a fleet-wide list, which made every node's measurement a write to one object shared by all of them.

Appears in: - StorageNodeStatus

Example:

nodeUUID: string
baselineP50NS: integer
baselineP99NS: integer
baselineMeasuredAt: Time
Field Description Default Validation
nodeUUID string NodeUUID is the backend storage node the reading was taken against. It is
carried beside the reading rather than inferred from status.uuid, because a
baseline measured against one backend node stops describing the slot once a
replacement fills it.
Required: {}
baselineP50NS integer BaselineP50NS is the p50 write latency, in nanoseconds, of the initial
empty-cluster benchmark.
Minimum: 0
Optional: {}
baselineP99NS integer BaselineP99NS is the p99 write latency, in nanoseconds, of the same
benchmark.
Minimum: 0
Optional: {}
baselineMeasuredAt Time ⧉ BaselineMeasuredAt is when the baseline was established. Optional: {}

NodeLoadMetrics

NodeLoadMetrics is one storage node's latency deviation, as the last evaluation cycle measured it.

Appears in: - RebalancingMetrics

Example:

nodeUUID: string
latencyDeviationPct: float
volumeCount: integer
lastUpdated: Time
Field Description Default Validation
nodeUUID string
latencyDeviationPct float
volumeCount integer
lastUpdated Time ⧉

NodeSet

NodeSet is the organizational grouping of a deployment, usually a rack: the workers a document adds or grows together. It carries no sizing, because sizing is uniform across a cluster and is stated once in ClusterTemplate.

Appears in: - ClusterDeploymentConfigSpec - StorageSiteDraft

Example:

name: string
groups:
  - name: string
    workers:
      - string
    mgmtInterface: string
    dataInterfaces:
      - string
    devices:
      nvme:
        - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
      block:
        - '^/dev/[a-zA-Z0-9._/-]+$'
    failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
    spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
    reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
    journalManager:
      count: integer
      percentPerDevice: integer
Field Description Default Validation
name string Name is the node set's name. It is copied to StorageNode.spec.nodeSet, so
that a node can be traced back to the part of the document that produced
it.
MaxLength: 253
Required: {}
groups NodeGroup array Groups are the sets of workers sharing one configuration. MaxItems: 64
MinItems: 1
Required: {}

OpenShiftSpec

OpenShiftSpec is what a deployment onto OpenShift states beyond what every distribution states.

Its presence is the statement. A cluster carrying the block runs on OpenShift and one without it does not, which is why there is no boolean beside it: a block naming a machine-config pool on a cluster that also said it was not OpenShift was expressible before and meant nothing.

Appears in: - ClusterTemplate - StorageNodesSpec

Example:

machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
Field Description Default Validation
machineConfigPool string MachineConfigPool names a machine-config role the storage nodes' own pool
inherits from, beyond the worker role it always inherits.
It is not the pool the nodes end up in, which the description it carried
before said and which cost a reader the reboot they were trying to avoid.
Adding a node creates a pool of its own, storage-, and moves the
node into it; a node belongs to exactly one custom pool, so whatever
machine configuration its previous pool carried is lost unless that
pool's role is named here for the new one to select as well. The default
is the role every pool already selects, which is what makes it a no-op
for a fleet whose workers are ordinary workers.
worker MaxLength: 253
Pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
Optional: {}

OperatorOps

OperatorOps is a single operation performed against the operator itself, which today means a discovery run that writes a ClusterDeploymentConfig. It runs to a terminal phase and stays afterward as the audit record.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: OperatorOps
metadata:
  name: string
spec:
  action: OperatorOpsAction
  abort: boolean
  discover:
    configName: string
    workers:
      - string
    nodeSelector:
      string: string
    tolerations:
      - Toleration
    enableControlPlaneNodes: boolean
    enableLogicalBlockDevices: boolean
    enablePartitionedDevices: boolean
    forceJournalDevice: boolean
    deviceFilter:
      pcieAllowList:
        - string
      pcieDenyList:
        - string
      pcieModel: string
      blockAllowList:
        - '^/dev/[a-zA-Z0-9._/-]+$'
      blockDenyList:
        - '^/dev/[a-zA-Z0-9._/-]+$'
      driveSizeRange: string
    clusterRef: string
status:
  phase: OperatorOpsPhase
  step: KubeSnapshot
  configRef: string
  workers:
    - string
  environment: KubernetesEnvironment
  message: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string OperatorOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec OperatorOpsSpec
status OperatorOpsStatus

OperatorOpsAction

Underlying type: string

OperatorOpsAction is the operation an OperatorOps performs.

The field stays with one value in it. Every Ops kind in this group is dispatched on spec.action, so a kind that left its single action implicit would be the one kind whose shape has to change the day it gains a second.

Validation: - Enum: [Discover]

Appears in: - OperatorOpsSpec

Field Description
Discover

OperatorOpsPhase

Underlying type: string

OperatorOpsPhase is the operation's own progress.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - OperatorOpsStatus

Field Description
Pending
Running
Succeeded
Failed
Aborted

OperatorOpsSpec

OperatorOpsSpec is one operation to perform against the operator itself.

It carries no target reference. Every other Ops kind in this group names the entity it acts on; this one acts on the operator process, which is not a resource in this API group, and the absent field is the signal.

Appears in: - OperatorOps

Example:

action: OperatorOpsAction
abort: boolean
discover:
  configName: string
  workers:
    - string
  nodeSelector:
    string: string
  tolerations:
    - Toleration
  enableControlPlaneNodes: boolean
  enableLogicalBlockDevices: boolean
  enablePartitionedDevices: boolean
  forceJournalDevice: boolean
  deviceFilter:
    pcieAllowList:
      - string
    pcieDenyList:
      - string
    pcieModel: string
    blockAllowList:
      - '^/dev/[a-zA-Z0-9._/-]+$'
    blockDenyList:
      - '^/dev/[a-zA-Z0-9._/-]+$'
    driveSizeRange: string
  clusterRef: string
Field Description Default Validation
action OperatorOpsAction Action is the operation to perform. Enum: [Discover]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind.
Whether an abort is expressible from the current step is declared by that
action's graph rather than checked here. Discovery changes nothing, so an
aborted run leaves behind at most the config it had already written.
Optional: {}
discover DiscoverSpec Discover parameterizes action Discover. Optional: {}

OperatorOpsStatus

OperatorOpsStatus is the observed state of one operator operation.

Appears in: - OperatorOps

Example:

phase: OperatorOpsPhase
step: KubeSnapshot
configRef: string
workers:
  - string
environment: KubernetesEnvironment
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase OperatorOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. Optional: {}
configRef string ConfigRef names the ClusterDeploymentConfig a Discover run wrote. Optional: {}
workers string array Workers are the workers this run is inspecting, decided once in
Inspecting so that a node joining the cluster mid-run does not change
what the run is about.
Optional: {}
environment KubernetesEnvironment Environment is the Kubernetes distribution Inspecting concluded, which
Writing copies into the draft. It is recorded here as well so that a run
that failed later still says what it found.
Enum: [Vanilla OpenShift Rancher K3s Talos]
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation started. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

PersistentVolumeOps

PersistentVolumeOps is a single operation performed against one PersistentVolume. It is the one Ops kind in this group whose target is a core Kubernetes type rather than a kind this group defines, so it locks its target with an annotation rather than a status field, is cluster-scoped because its target is, cannot be owned by the namespaced operation that created it, and derives its cluster, pool, and volume from the volume's CSI handle rather than being told.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: PersistentVolumeOps
metadata:
  name: string
spec:
  persistentVolumeName: string
  action: PersistentVolumeOpsAction
  abort: boolean
  migrate:
    targetNodeRef:
      namespace: string
      name: string
  creatorRef:
    kind: string
    namespace: string
    name: string
    uid: UID
status:
  phase: PersistentVolumeOpsPhase
  step: KubeSnapshot
  migration:
    migrationUUID: string
    clusterUUID: string
    poolUUID: string
    volumeUUID: string
    subsystemNQN: string
    sourceNodeUUID: string
    targetNodeUUID: string
    continuedAt: Time
    memberCount: integer
    connections:
      - nqn: string
        address: string
        port: integer
        transport: string
        nrIOQueues: integer
        reconnectDelaySeconds: integer
        ctrlLossTimeoutSeconds: integer
        fastIOFailTimeoutSeconds: integer
        keepAliveTimeoutSeconds: integer
    validationJobs:
      - namespace: string
        name: string
        node: string
        succeeded: boolean
  deferredSince: Time
  message: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string PersistentVolumeOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec PersistentVolumeOpsSpec
status PersistentVolumeOpsStatus

PersistentVolumeOpsAction

Underlying type: string

PersistentVolumeOpsAction is the operation a PersistentVolumeOps performs. The kind is named for its target rather than for the action so that carrying a second one later would not rename it. It carries one, and no second one is planned.

Validation: - Enum: [Migrate]

Appears in: - PersistentVolumeOpsSpec

Field Description
Migrate PersistentVolumeOpsActionMigrate moves the volume's backing logical
volume to a different storage node.

PersistentVolumeOpsPhase

Underlying type: string

PersistentVolumeOpsPhase is the operation's own progress. Succeeded rather than the registered kind's Completed, so that every Ops kind in this group reports the same five phases and an alert on completion matches one value.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - PersistentVolumeOpsStatus

Field Description
Pending PersistentVolumeOpsPhasePending is an operation waiting for the volume's
lock, or holding nothing yet.
Running PersistentVolumeOpsPhaseRunning is an operation holding the lock and
working.
Succeeded PersistentVolumeOpsPhaseSucceeded is a finished operation that did what
it said.
Failed PersistentVolumeOpsPhaseFailed is a finished operation that did not.
Aborted PersistentVolumeOpsPhaseAborted is an operation stopped on request, or
stopped because its volume went away, whose unwind has finished.

PersistentVolumeOpsSpec

PersistentVolumeOpsSpec is one operation to perform against one PersistentVolume.

The action and its parameter block have to agree: Migrate requires spec.migrate, and no other action accepts it.

Appears in: - PersistentVolumeOps

Example:

persistentVolumeName: string
action: PersistentVolumeOpsAction
abort: boolean
migrate:
  targetNodeRef:
    namespace: string
    name: string
creatorRef:
  kind: string
  namespace: string
  name: string
  uid: UID
Field Description Default Validation
persistentVolumeName string PersistentVolumeName names the PersistentVolume this operation acts on.
It is a name rather than a reference because a PersistentVolume is
cluster-scoped, and it names the volume rather than the claim because a
claim can be deleted while its volume is retained — under a Retain
reclaim policy the logical volume still occupies capacity on a node that
may be draining, and is still worth moving.
Required: {}
action PersistentVolumeOpsAction Action is the operation to perform. Immutable: an operation that changed
what it was doing halfway through would have a status describing neither.
Enum: [Migrate]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind. It is
expressible from Validating and Migrating and not from Verifying, which
the action's graph declares rather than this field: once the copy has
finished, the volume has moved and there is nothing to undo.
Optional: {}
migrate MigrateVolumeSpec Migrate parameterizes action Migrate. Optional: {}
creatorRef CreatorReference CreatorRef names the object that created this one, and that object's
finalizer is what aborts and deletes this one when it goes. Absent on an
operation written by hand, which has no creator to cascade from.
Immutable, because an operation changing whose fan-out it belongs to
would change who cascades over it.
Optional: {}

PersistentVolumeOpsStatus

PersistentVolumeOpsStatus is the observed state of one volume operation.

Appears in: - PersistentVolumeOps

Example:

phase: PersistentVolumeOpsPhase
step: KubeSnapshot
migration:
  migrationUUID: string
  clusterUUID: string
  poolUUID: string
  volumeUUID: string
  subsystemNQN: string
  sourceNodeUUID: string
  targetNodeUUID: string
  continuedAt: Time
  memberCount: integer
  connections:
    - nqn: string
      address: string
      port: integer
      transport: string
      nrIOQueues: integer
      reconnectDelaySeconds: integer
      ctrlLossTimeoutSeconds: integer
      fastIOFailTimeoutSeconds: integer
      keepAliveTimeoutSeconds: integer
  validationJobs:
    - namespace: string
      name: string
      node: string
      succeeded: boolean
deferredSince: Time
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase PersistentVolumeOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. It is
persisted before the side effect that step performs, so a step reported
here is a step that started.
Optional: {}
migration MigrationStatus Migration is everything about the migration rather than about the
operation.
Optional: {}
deferredSince Time ⧉ DeferredSince is when the operation was first held — behind another
operation's lock, or behind a control plane that is not accepting
migrations yet. It is what the auto-rebalancer reads to decide whether a
migration has waited long enough to give up on, and it is in status
rather than in memory because the operator may restart and an observer
needs to see that the operation is waiting and since when.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation began. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

PoolLimits

PoolLimits are the ceilings the pool as a whole is held to. They are the pool's budget rather than a volume's: a volume's own defaults are in StoragePoolSpec.VolumeDefaults, and the two use the same units so that a reader can compare them.

Appears in: - StoragePoolSpec

Example:

capacity: string
maxVolumeSize: string
iops: integer
throughput:
  read: integer
  write: integer
  readWrite: integer
Field Description Default Validation
capacity string Capacity is the total capacity the pool may allocate, written the way an
administrator writes one: 10T, 500G. Empty is unlimited.
Optional: {}
maxVolumeSize string MaxVolumeSize is the largest single logical volume the pool will create.
Empty is unlimited.
Optional: {}
iops integer IOPS is the pool-wide ceiling on operations per second, both directions
together. Zero is unlimited, which is the control plane's own convention
for these values.
Minimum: 0
Optional: {}
throughput ThroughputLimits Throughput is the pool-wide throughput ceiling. Optional: {}

PoolLimitsStatus

PoolLimitsStatus is what the control plane reports the pool's ceilings actually are, which is not necessarily what Limits asked for.

Appears in: - StoragePoolStatus

Example:

host: string
iops: integer
throughput:
  read: integer
  write: integer
  readWrite: integer
Field Description Default Validation
host string Host is the backend host enforcing the pool's QoS. Optional: {}
iops integer IOPS is the combined ceiling the control plane is enforcing. Optional: {}
throughput ThroughputLimits Throughput is the throughput ceiling the control plane is enforcing. Optional: {}

ProvisioningSlot

StorageClusterStatus is the observed state of one backend cluster. ProvisioningSlot is one worker's hold on the cluster's node-add concurrency, held from the moment its add is posted until the node has a backend UUID or has given up.

Appears in: - StorageClusterStatus

Example:

worker: string
node: string
takenAt: Time
Field Description Default Validation
worker string Worker is the Kubernetes node the add was posted for, and it is what the
cap counts.
node string Node is the StorageNode object that took the slot. A release removes only
the entry naming its own object, which is what keeps a node from freeing
somebody else's slot, and a slot whose object is gone is reaped.
takenAt Time ⧉ TakenAt is when the slot was taken, so a hold that outlives its node's
deadlines is visible in the object rather than only in the events.
Optional: {}

RebalancingMetrics

RebalancingMetrics is written by the auto-rebalancer each evaluation cycle.

Appears in: - StorageClusterStatus

Example:

avgDeviationPct: float
maxDeviationPct: float
hottestNodeUUID: string
coolestNodeUUID: string
imbalancePercent: float
lastEvaluatedAt: Time
lastMigrationAt: Time
nodeMetrics:
  - nodeUUID: string
    latencyDeviationPct: float
    volumeCount: integer
    lastUpdated: Time
Field Description Default Validation
avgDeviationPct float AvgDeviationPct is the mean latency deviation across the cluster's nodes.
maxDeviationPct float MaxDeviationPct is the highest per-node latency deviation, which is what
ImbalancePercent reports.
hottestNodeUUID string
coolestNodeUUID string
imbalancePercent float
lastEvaluatedAt Time ⧉
lastMigrationAt Time ⧉
nodeMetrics NodeLoadMetrics array

RemovalStatus

RemovalStatus is the control plane's progress through a node removal it has accepted, as the operation last read it while waiting in AwaitingRemoval.

Any change in it counts as progress. Each change moves the step's deadline out by the full budget again, so a removal that keeps moving is waited on for as long as it moves, and one that stops is failed once a full budget passes with nothing changing.

Appears in: - StorageNodeOpsStatus

Example:

nodeStatus: string
devices:
  string: string
lastProgressTime: Time
prepareAttempts: integer
lastPrepareTime: Time
Field Description Default Validation
nodeStatus string NodeStatus is the node's status as the control plane last reported it,
in the control plane's own spelling: pending_removal, migrating_devices,
migrating_lvols, in_removal, removed, or removed_failed.
Optional: {}
devices object (keys:string, values:string) Devices is the control-plane status each of the node's StorageDevices
last reported, keyed by the StorageDevice's name. A device moves from
online through failed to failed_and_migrated, or to removed, as the
removal rebuilds its data onto the peers.
Optional: {}
lastProgressTime Time ⧉ LastProgressTime is when NodeStatus or any of Devices last changed. Optional: {}
prepareAttempts integer PrepareAttempts is how many times the removal's first step was sent
again for a node that stayed pending_removal, because the control plane
reported the step failed or nothing moved for longer than a shutdown
takes. The operation fails once the attempts run out.
Minimum: 0
Optional: {}
lastPrepareTime Time ⧉ LastPrepareTime is when the removal's first step was last sent again. Optional: {}

RemoveSpec

RemoveSpec parameterizes the Remove action and is ignored by the others.

Appears in: - StorageNodeOpsSpec

Example:

systemVolumeFilterRegex: string
Field Description Default Validation
systemVolumeFilterRegex string SystemVolumeFilterRegex matches backend volume names that are system
volumes: excluded from the drain's migration and deleted during
verification rather than blocking it.
^sb-fio-baseline-.* Optional: {}

RestartSpec

RestartSpec parameterizes the Restart action and is ignored by the others.

Appears in: - ControlPlaneOpsSpec

Example:

components:
  - string
Field Description Default Validation
components string array Components names the workloads to recycle. Empty recycles the whole
control plane. Naming only components that status.components reports as
non-essential skips the drain, because recycling them interrupts nothing.
Optional: {}

RestoreOpsSpec

RestoreOpsSpec carries the parameters of the Restore action.

Appears in: - VolumeGroupSnapshotOpsSpec

Example:

namePrefix: string
storageClassName: string
consistencyGroup: string
enablePartialRestore: boolean
Field Description Default Validation
namePrefix string NamePrefix prefixes every restored claim's name:
-, the source name read from the member
snapshot's spec.source.persistentVolumeClaimName. Defaults to the
operation's own name.
Optional: {}
storageClassName string StorageClassName is the class every restored claim requests. When empty,
each claim inherits the class of its member snapshot's source claim, and
the restore fails for a member whose source claim no longer exists.
Optional: {}
consistencyGroup string ConsistencyGroup labels every restored claim with
storage.simplyblock.io/consistency-group: , so the clones form a
new group at provisioning under the mandatory placement rule. Empty
leaves the clones as independent, mutually consistent volumes.
Optional: {}
enablePartialRestore boolean EnablePartialRestore restores the members an incomplete generation still
has instead of failing the operation. Off by default: an incomplete
generation fails, naming the missing members.
Optional: {}

RestoreSpec

RestoreSpec parameterizes the Restore action.

It carries no size, no access mode, and no volume mode, which the registered BackupRestore took as a whole PersistentVolumeClaim template. All three are facts about the backup rather than choices: a restored volume is the size of the copy, and asking for a different one is either a truncation or a lie. The controller reads the size off status.backup.size and mounts the filesystem status.source.fsType records.

Appears in: - StorageBackupOpsSpec

Example:

claimName: string
targetPool: string
claimLabels:
  string: string
claimAnnotations:
  string: string
Field Description Default Validation
claimName string ClaimName is the PersistentVolumeClaim to create. It must not already
exist: a restore that adopted an existing claim would replace a running
workload's data with the backup's.
Required: {}
targetPool string TargetPool is the StoragePool to restore into. It is required rather than
defaulted: a backup found in the store may have been written by another
cluster, so status.source.poolName names a pool this cluster need not
have, and which pool a volume lands in is a tenancy and QoS decision
nobody should make by omission.
Required: {}
claimLabels object (keys:string, values:string) ClaimLabels and ClaimAnnotations are applied to the created claim, so that
a restored volume can be selected by a policy or an application the same
way its original was.
Immutable with the rest of the block. The claim is written at the last
step, so a value edited while the operation waited for its volume would
produce a claim built from inputs the admitted and audited operation never
carried, which is the audit record disagreeing with what happened.
Optional: {}
claimAnnotations object (keys:string, values:string) Optional: {}

RestoredMemberStatus

RestoredMemberStatus records one member snapshot and the claim restored from it.

Appears in: - VolumeGroupSnapshotOpsStatus

Example:

volumeSnapshotName: string
persistentVolumeClaimName: string
bound: boolean
Field Description Default Validation
volumeSnapshotName string VolumeSnapshotName is the member VolumeSnapshot the claim restores from.
persistentVolumeClaimName string PersistentVolumeClaimName is the restored claim.
bound boolean Bound reports whether the restored claim has bound. Optional: {}

RollingRestartSpec

RollingRestartSpec parameterizes the RollingRestart action.

Appears in: - StorageClusterOpsSpec

Example:

refreshSNodeAPI: boolean
Field Description Default Validation
refreshSNodeAPI boolean RefreshSNodeAPI restarts each node's storage-node DaemonSet pod between
its shutdown and its restart, so the latest image is running when the
node returns.
Optional: {}

RollingRestartStatus

RollingRestartStatus is the walk's position over the cluster's nodes. Where the machine has got to within the node currently being restarted is status.step, and neither field is complete without the other.

Appears in: - StorageClusterOpsStatus

Example:

nodes:
  - string
nodeIndex: integer
skipped:
  - string
Field Description Default Validation
nodes string array Nodes is the ordered list of storage node UUIDs this action covers,
written once when the walk starts and not modified afterward, so a node
added mid-walk is not restarted and one removed mid-walk is skipped when
the walk reaches it.
Optional: {}
nodeIndex integer NodeIndex is the position in Nodes of the node being restarted. Advancing
the walk increments it, and the walk is complete when it reaches
len(Nodes).
No omitempty: zero is a valid index, and a field that disappears at zero
makes "the first node" and "unset" the same wire value.
Minimum: 0
skipped string array Skipped are the nodes of Nodes the walk passed over without restarting
them, in walk order: a node the control plane stopped listing, or one
whose removal started after the walk was planned. A node leaving the
cluster belongs to its removal, and the walk sends it nothing.
Optional: {}

SidecarImages

SidecarImages overrides the CSI sidecar images this deployment runs. An unset field takes the version this operator release ships, which is the combination it was tested against, and the fields exist so that a pin a Helm release made survives the adoption of that release's deployment.

Every field carries the registry pattern Image carries. The node plugin is privileged and mounts /dev, /sys, and the kubelet's plugin directory from the host, so a sidecar beside it runs with the same access.

Appears in: - SimplyblockDriverSpec

Example:

provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
Field Description Default Validation
provisioner string Provisioner is csi-provisioner, on the controller plugin. Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
attacher string Attacher is csi-attacher, on the controller plugin. Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
resizer string Resizer is csi-resizer, on the controller plugin. Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
snapshotter string Snapshotter is csi-snapshotter, on the controller plugin. It is this
driver's sidecar and not the cluster's snapshot-controller, whose image
is not overridable here because that component belongs to the cluster.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
healthMonitor string HealthMonitor is csi-external-health-monitor-controller, on the
controller plugin.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
nodeDriverRegistrar string NodeDriverRegistrar is node-driver-registrar, on the node plugin. Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
csiAddons string CSIAddons is the kubernetes-csi-addons sidecar, on the controller
plugin. It connects to the plugin's socket, probes the csi-addons
Identity service for capabilities, and publishes a CSIAddonsNode so the
kubernetes-csi-addons controller-manager (design
design-csi-addons-replication.md §4.1) can reach the Replication
service this driver serves.
Unlike the other sidecars above, this one's upstream home is the
csi-addons project's own registry, not simplyblock's: the allowlist
carries quay.io/csiaddons alongside the simplyblock registries so a
deployment can run the stock kubernetes-csi-addons sidecar image
directly, ahead of (or instead of) a quay.io/simplyblock-io mirror.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block\|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}

SimplyblockDriver

SimplyblockDriver is the deployment of simplyblock's CSI driver: the node plugin, the controller plugin, their RBAC, and the core CSIDriver registration they produce. It is named for the brand rather than the interface because CSIDriver is already a kind in core storage.k8s.io/v1, and the two are not the same object: the core kind is the cluster's registration record, and this one is the deployment that produces it.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: SimplyblockDriver
metadata:
  name: string
spec:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
  driverName: string
  controllerReplicas: integer
  nodeSelector:
    string: string
  tolerations:
    - Toleration
  controllerNodeSelector:
    string: string
  controllerTolerations:
    - Toleration
  controllerResources: ResourceRequirements
  nodeResources: ResourceRequirements
  sidecarImages:
    provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  enableServiceAccountAuth: boolean
  enableVolumeSnapshots: boolean
  tls:
    enableTLS: boolean
    enableMutualTLS: boolean
    provider: DriverTLSProvider
  pnfs:
    enablePNFS: boolean
status:
  phase: SimplyblockDriverPhase
  snapshotSupport: SnapshotSupportOrigin
  origin: SimplyblockDriverOrigin
  version: string
  nodesReady: integer
  nodesTotal: integer
  controllerReady: boolean
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string SimplyblockDriver
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec SimplyblockDriverSpec
status SimplyblockDriverStatus

SimplyblockDriverOrigin

Underlying type: string

SimplyblockDriverOrigin is where the running deployment came from. Every cluster upgraded from a chart install reads Adopted, because the chart had applied the objects before this kind existed.

Validation: - Enum: [Created Adopted]

Appears in: - SimplyblockDriverStatus

Field Description
Created SimplyblockDriverOriginCreated is a deployment whose objects the operator
applied from nothing.
Adopted SimplyblockDriverOriginAdopted is a deployment the operator took over in
place, taking field ownership from Helm and removing the release's
metadata once the handover was verified.

SimplyblockDriverPhase

Underlying type: string

SimplyblockDriverPhase is where the operator has got to with the CSI driver. Installing covers the applies, and the three values after it are decided by what the node plugins and the controller plugin report.

Validation: - Enum: [Installing Ready Degraded Unavailable]

Appears in: - SimplyblockDriverStatus

Field Description
Installing SimplyblockDriverPhaseInstalling is a deployment whose objects are not all
applied yet.
Ready SimplyblockDriverPhaseReady is every plugin pod serving.
Degraded SimplyblockDriverPhaseDegraded is a node plugin restarting while the
controller plugin still provisions, which strands one worker's volumes
rather than the namespace's.
Unavailable SimplyblockDriverPhaseUnavailable is a controller plugin that is not
running, which is when provisioning stops.

SimplyblockDriverSpec

SimplyblockDriverSpec is the CSI driver deployment: the node plugin, the controller plugin, their RBAC, and the CSIDriver registration they produce.

Appears in: - SimplyblockDriver

Example:

image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
driverName: string
controllerReplicas: integer
nodeSelector:
  string: string
tolerations:
  - Toleration
controllerNodeSelector:
  string: string
controllerTolerations:
  - Toleration
controllerResources: ResourceRequirements
nodeResources: ResourceRequirements
sidecarImages:
  provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
enableServiceAccountAuth: boolean
enableVolumeSnapshots: boolean
tls:
  enableTLS: boolean
  enableMutualTLS: boolean
  provider: DriverTLSProvider
pnfs:
  enablePNFS: boolean
Field Description Default Validation
image string Image is the CSI driver image, used by both plugins. Unset takes the
operator's own registry and tag with the CSI driver's repository, so a
deployment that states nothing runs the driver belonging to the operator
reconciling it, which is the pairing the release was tested as.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
imagePullPolicy PullPolicy ⧉ ImagePullPolicy controls when that image is pulled. It defaults to Always
because the default Image is a moving tag: it follows the operator's own,
and a development build's tag is rebuilt in place. IfNotPresent against a
tag that moved leaves the workers that already pulled it running the old
plugin and the workers that had not running the new one, which is version
skew inside one deployment and invisible from the object.
Always Enum: [Always Never IfNotPresent]
Optional: {}
driverName string DriverName is the CSI driver name a StorageClass provisions with. Every
PersistentVolume the driver created records it in spec.csi.driver and
every VolumeAttachment records it too, so changing it orphans every volume
in the namespace rather than renaming anything.
csi.simplyblock.io Optional: {}
controllerReplicas integer ControllerReplicas is the number of controller-plugin instances. 1 Minimum: 1
Optional: {}
nodeSelector object (keys:string, values:string) NodeSelector restricts which workers run the node plugin. Empty means
every schedulable worker, which is the usual case: a node that cannot
attach a volume cannot run a workload that needs one.
Optional: {}
tolerations Toleration ⧉ array Tolerations are applied to the node plugin, which usually needs to run
where workloads run rather than where the operator does.
Optional: {}
controllerNodeSelector object (keys:string, values:string) ControllerNodeSelector and ControllerTolerations place the controller
plugin. The unprefixed pair above is the node plugin's, because that
placement decides which workers can attach a volume, and this pair is
ordinary pod placement for the one workload that provisions them.
Optional: {}
controllerTolerations Toleration ⧉ array Optional: {}
controllerResources ResourceRequirements ⧉ ControllerResources and NodeResources set requests and limits for the two
plugins. Unset enforces no limits.
Optional: {}
nodeResources ResourceRequirements ⧉ Optional: {}
sidecarImages SidecarImages SidecarImages overrides the seven CSI sidecars, one field each. Unset
takes the version this operator release ships.
Optional: {}
enableServiceAccountAuth boolean EnableServiceAccountAuth makes both plugins authenticate to the management
API with their pod's Kubernetes service-account token instead of the
static cluster secret. The control plane has to list those accounts in
SB_K8S_ADMIN_SERVICE_ACCOUNTS for it to work, which is why this is a
deployment-wide switch rather than a per-plugin one.
false Optional: {}
enableVolumeSnapshots boolean EnableVolumeSnapshots decides whether snapshot support is part of this
deployment: the VolumeSnapshotClass for DriverName, and the CRDs and a
controller where the cluster serves neither. False applies none of them.
true Optional: {}
tls DriverTLS TLS configures whether both plugins reach the control plane over TLS.
Unset is plaintext, the shape every deployment ran before this field
existed. Adopting a deployment that already runs TLS requires this to
agree with what its plugins are configured for, because it is not read
off the running objects.
Optional: {}
pnfs DriverPNFS PNFS configures pNFS support on the node plugin. Optional: {}

SimplyblockDriverStatus

SimplyblockDriverStatus is the observed state of the CSI driver deployment.

Appears in: - SimplyblockDriver

Example:

phase: SimplyblockDriverPhase
snapshotSupport: SnapshotSupportOrigin
origin: SimplyblockDriverOrigin
version: string
nodesReady: integer
nodesTotal: integer
controllerReady: boolean
message: string
observedGeneration: integer
Field Description Default Validation
phase SimplyblockDriverPhase Phase is the operator's own view of the deployment. Enum: [Installing Ready Degraded Unavailable]
Optional: {}
snapshotSupport SnapshotSupportOrigin SnapshotSupport is whether the cluster already had snapshot support or the
operator installed it, which is what says whether other drivers depend on
what this one applied.
Enum: [Detected Installed]
Optional: {}
origin SimplyblockDriverOrigin Origin is whether the first reconcile created this deployment's objects
or met ones it did not create. It is decided once and never revised,
because what it records is where the running deployment came from rather
than what the controller did most recently.
Enum: [Created Adopted]
Optional: {}
version string Version is the version the deployed driver reports, published so that a
skew against ControlPlane.status.version is visible on one screen.
Optional: {}
nodesReady integer NodesReady is how many workers run a ready node plugin, and NodesTotal how
many are expected to. Neither takes omitempty: zero ready plugins is the
condition worth seeing.
Minimum: 0
nodesTotal integer Minimum: 0
controllerReady boolean ControllerReady is whether the controller plugin is serving. Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the deployment moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

SnapshotSupportOrigin

Underlying type: string

SnapshotSupportOrigin is where the cluster's snapshot support came from.

Validation: - Enum: [Detected Installed]

Appears in: - SimplyblockDriverStatus

Field Description
Detected SnapshotSupportOriginDetected is a cluster that already served
snapshot.storage.k8s.io/v1, so the operator applied no CRDs and no
controller.
Installed SnapshotSupportOriginInstalled is a cluster where the operator applied
them. They are cluster-scoped and shared, so they carry no controller
reference and outlive this object.

StorageBackup

StorageBackup is one point-in-time copy of one volume that exists in a cluster's store.

Objects are created by the operator from what the store holds and are never written by a user: creating one by hand would claim a backup exists that the store does not hold, and deleting one would hide a backup that is still there and still restorable. Deleting the object never deletes the copy.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackup
metadata:
  name: string
spec:
  clusterRef: string
  backupID: string
status:
  phase: StorageBackupPhase
  apiStatus: string
  clusterID: string
  backup:
    backupID: string
    s3ID: integer
    size: integer
    previousBackupID: string
    startedAt: Time
    completedAt: Time
  source:
    claimName: string
    claimNamespace: string
    persistentVolumeName: string
    poolName: string
    poolUUID: string
    lvolID: string
    lvolName: string
    fsType: string
    snapshotID: string
    snapshotName: string
    nodeID: string
    clusterUUID: string
  activeOpsRef: string
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageBackup
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata. Optional: {}
spec StorageBackupSpec spec identifies the backup this object reports on Required: {}
status StorageBackupStatus status defines the observed state of StorageBackup Optional: {}

StorageBackupOps

StorageBackupOps is a single operation performed against one StorageBackup. It runs to a terminal phase and stays afterward as the audit record of what was restored, into which pool, and how it ended.

The claim a restore produces is not owned by the operation and outlives it: nobody restores a backup in order to keep a StorageBackupOps, so deleting the audit record must not delete the recovered volume.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupOps
metadata:
  name: string
spec:
  clusterRef: string
  backupRef: string
  action: StorageBackupOpsAction
  abort: boolean
  restore:
    claimName: string
    targetPool: string
    claimLabels:
      string: string
    claimAnnotations:
      string: string
status:
  phase: StorageBackupOpsPhase
  step: KubeSnapshot
  clusterID: string
  backupID: string
  restoredLvolID: string
  poolUUID: string
  persistentVolumeName: string
  claimName: string
  message: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageBackupOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata. Optional: {}
spec StorageBackupOpsSpec spec defines the operation to perform Required: {}
status StorageBackupOpsStatus status defines the observed state of StorageBackupOps Optional: {}

StorageBackupOpsAction

Underlying type: string

StorageBackupOpsAction is the operation a StorageBackupOps performs. Restore acts on a StorageBackup, which is every backup in the cluster's store.

Validation: - Enum: [Restore]

Appears in: - StorageBackupOpsSpec

Field Description
Restore

StorageBackupOpsPhase

Underlying type: string

StorageBackupOpsPhase is the operation's own progress. It is the same small set every Ops kind in the group carries: Pending before the operation holds its target's lock, Running while it works, and three terminal values.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - StorageBackupOpsStatus

Field Description
Pending
Running
Succeeded
Failed
Aborted

StorageBackupOpsSpec

StorageBackupOpsSpec is one operation to perform against a backup.

Appears in: - StorageBackupOps

Example:

clusterRef: string
backupRef: string
action: StorageBackupOpsAction
abort: boolean
restore:
  claimName: string
  targetPool: string
  claimLabels:
    string: string
  claimAnnotations:
    string: string
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster the operation runs against.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Required: {}
backupRef string BackupRef names the StorageBackup this operation acts on, in this
namespace. Required, since Restore is the only action and every backup in
the store has an object.
Required: {}
action StorageBackupOpsAction Action is the operation to perform. Enum: [Restore]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind. It is
the one field of this spec that may be edited after the object is created,
because it is the one thing about an operation that can legitimately be
decided after it started.
A restore cannot be aborted once it has created a logical volume, and the
action's graph declares that rather than this field: an abort arriving
later is reported as an illegal transition while the operation runs on,
rather than leaving the work half undone.
Optional: {}
restore RestoreSpec Restore parameterizes action Restore. Optional: {}

StorageBackupOpsStatus

StorageBackupOpsStatus is the observed state of one backup operation.

Appears in: - StorageBackupOps

Example:

phase: StorageBackupOpsPhase
step: KubeSnapshot
clusterID: string
backupID: string
restoredLvolID: string
poolUUID: string
persistentVolumeName: string
claimName: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase StorageBackupOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. It is
persisted before the side effect that step performs, so a step reported
here is a step that started.
Optional: {}
clusterID string ClusterID is the backend cluster the operation ran against. Optional: {}
backupID string BackupID is the copy the restore read from, recorded so the operation says
what it restored after the object list has moved on.
Optional: {}
restoredLvolID string RestoredLvolID is the logical volume the control plane created. It is
written before the claim, so a restarted Binding step knows what it is
binding.
Optional: {}
poolUUID string PoolUUID is the backend identifier of spec.restore.targetPool, resolved
once at Validating.
Optional: {}
persistentVolumeName string PersistentVolumeName is the PV the operation wrote for the restored
volume.
Optional: {}
claimName string ClaimName is the PersistentVolumeClaim a Restore produced. It is written
before the claim is created rather than after, because the claim carries
no owner reference back: a restarted Binding step recognizes its own work
by this name together with the storage.simplyblock.io/restored-by label on
the claim, and refuses a claim of the right name that carries neither.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from. On this kind it advances at most twice, and the second advance is
precisely the signal that spec.abort has been observed.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

StorageBackupPhase

Underlying type: string

StorageBackupPhase is where the operator has got to with this backup. Available is the terminal success rather than Succeeded, because a backup is not an operation: what matters afterward is that the copy can be restored, not that the copying finished.

Validation: - Enum: [Pending Creating Available Failed]

Appears in: - StorageBackupStatus

Field Description
Pending
Creating
Available
Failed

StorageBackupPolicy

StorageBackupPolicy schedules and retains the backups of the claims it selects. It is the only thing that decides a backup is taken; the copies themselves are the control plane's, are pruned by its retention, and reach Kubernetes as StorageBackup objects the operator discovers.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupPolicy
metadata:
  name: string
spec:
  clusterRef: string
  claimSelector: LabelSelector
  schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$'
  maxVersions: integer
  maxAge: '^[1-9]\d*[mhdw]$'
status:
  phase: StorageBackupPolicyPhase
  clusterID: string
  policyID: string
  attachedClaims:
    - name: string
      persistentVolumeName: string
      lvolID: string
      attachedAt: Time
  lastBackupAt: Time
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageBackupPolicy
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata. Optional: {}
spec StorageBackupPolicySpec spec defines the desired state of StorageBackupPolicy Required: {}
status StorageBackupPolicyStatus status defines the observed state of StorageBackupPolicy Optional: {}

StorageBackupPolicyPhase

Underlying type: string

StorageBackupPolicyPhase is where the operator has got to with this policy.

Validation: - Enum: [Pending Active Failed]

Appears in: - StorageBackupPolicyStatus

Field Description
Pending
Active
Failed

StorageBackupPolicySpec

StorageBackupPolicySpec schedules and retains the backups of the claims it selects. The operator reconciles it into the control plane and reports what the control plane did; it does not run the schedule itself, because a backup schedule that stopped when the operator was down would be one nobody could rely on.

Appears in: - StorageBackupPolicy

Example:

clusterRef: string
claimSelector: LabelSelector
schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$'
maxVersions: integer
maxAge: '^[1-9]\d*[mhdw]$'
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster whose backup target this policy writes
to.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Required: {}
claimSelector LabelSelector ⧉ ClaimSelector selects the PersistentVolumeClaims this policy backs up. An
absent selector selects nothing, which is deliberate: the cost of backing
up too much is silent and recurring, and the cost of backing up too little
is an error somebody sees. The controller reports an inert policy with a
SelectorEmpty event rather than refusing it.
Optional: {}
schedule string Schedule is the tiered schedule the control plane runs the policy on, as a
space-separated list of interval,keep_count pairs ("15m,4 60m,11 24h,7").
Intervals must be strictly increasing, and the supported units are m, h,
d, and w.
Immutable, and that is a property of the control plane rather than a
choice. The control plane offers no call that applies a changed schedule:
it creates, deletes, attaches, and detaches a policy and nothing else. A
mutable field the operator cannot reconcile would leave the declaration
and the backups actually being taken permanently disagreeing, with the
object still reporting Active, so the schedule is fixed at creation until
that call exists. Changing one means replacing the policy.
Pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$
Optional: {}
maxVersions integer MaxVersions is how many backups of one claim to keep. Zero means no limit
by count. Immutable, for the reason Schedule is.
Minimum: 0
Optional: {}
maxAge string MaxAge is how long to keep a backup ("30d," "720h"). Empty means no limit
by age. Retention is enforced by the control plane, not here. Immutable,
for the reason Schedule is.
Pattern: ^[1-9]\d*[mhdw]$
Optional: {}

StorageBackupPolicyStatus

StorageBackupPolicyStatus is the observed state of the policy.

Appears in: - StorageBackupPolicy

Example:

phase: StorageBackupPolicyPhase
clusterID: string
policyID: string
attachedClaims:
  - name: string
    persistentVolumeName: string
    lvolID: string
    attachedAt: Time
lastBackupAt: Time
message: string
observedGeneration: integer
Field Description Default Validation
phase StorageBackupPolicyPhase Phase is the operator's own view of this policy. Enum: [Pending Active Failed]
Optional: {}
clusterID string ClusterID is the backend cluster the policy was created in. Optional: {}
policyID string PolicyID is the control plane's identifier for the policy. Optional: {}
attachedClaims AttachedClaim array AttachedClaims are the claims the policy currently covers. Detaching one
stops new backups being taken and deletes none of the existing ones.
Optional: {}
lastBackupAt Time ⧉ LastBackupAt is when the control plane last completed a backup under this
policy, and it is what an age alert is computed from.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the policy moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

StorageBackupSpec

StorageBackupSpec is the identity of one backup the operator found in a cluster's store, and nothing else. The object is created by the operator and by nobody else, so there is no request here to carry.

Appears in: - StorageBackup

Example:

clusterRef: string
backupID: string
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster whose store this backup was found in.
With BackupID it is the whole of this object's identity.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Required: {}
backupID string BackupID is the identifier the store holds the backup under, and what a
restore addresses. It is the store's identifier rather than a name this
operator assigns, so the same backup is the same object however many
clusters have the location configured.
Required: {}

StorageBackupStatus

StorageBackupStatus is the observed state of one backup, in three groups: the copy, what the volume was, and the operator's own view.

Appears in: - StorageBackup

Example:

phase: StorageBackupPhase
apiStatus: string
clusterID: string
backup:
  backupID: string
  s3ID: integer
  size: integer
  previousBackupID: string
  startedAt: Time
  completedAt: Time
source:
  claimName: string
  claimNamespace: string
  persistentVolumeName: string
  poolName: string
  poolUUID: string
  lvolID: string
  lvolName: string
  fsType: string
  snapshotID: string
  snapshotName: string
  nodeID: string
  clusterUUID: string
activeOpsRef: string
message: string
observedGeneration: integer
Field Description Default Validation
phase StorageBackupPhase Phase is the operator's own view of this backup. Enum: [Pending Creating Available Failed]
Optional: {}
apiStatus string APIStatus is the control plane's own lifecycle string, in the control
plane's spelling, which is why it carries no Enum here. It is what keeps
the states the four-value phase folds together — merging, deleting —
legible.
Optional: {}
clusterID string ClusterID is the backend cluster whose stream this backup was last
observed on. An object outliving its backup has to say which scope's
silence is authoritative before it may be deleted, and spec.clusterRef
names a Kubernetes object rather than a backend one. StorageDevice carries
the same field for the same reason.
Optional: {}
backup BackupCopy Backup is the copy itself. Optional: {}
source BackupSource Source is what the volume was when the copy was taken. Optional: {}
activeOpsRef string ActiveOpsRef names the StorageBackupOps currently allowed to act on this
backup. Empty when none is running.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the backup moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one. On this kind it
moves at most once, since every spec field is fixed when the object is
created.
Optional: {}

StorageCluster

StorageCluster is one simplyblock backend cluster. It owns the storage nodes beneath it, the pools carved out of it, and the Kubernetes workload its nodes run as.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageCluster
metadata:
  name: string
spec:
  maxSubsystemCount: integer
  vcpuCount: integer
  minHugePagesSize: string
  stripe:
    dataChunks: integer
    parityChunks: integer
  fabricType: string
  clientDataIfname: string
  nvmfBasePort: integer
  rpcBasePort: integer
  snodeApiPort: integer
  enableFailureDomains: boolean
  enableNodeAffinity: boolean
  enableChecksumValidation: boolean
  enableAtomicity4K: boolean
  deviceClass: StorageClusterDeviceClass
  kms:
    vault:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  warningThreshold:
    capacity: integer
    provisionedCapacity: integer
  criticalThreshold:
    capacity: integer
    provisionedCapacity: integer
  maxConcurrentWorkerRestarts: integer
  storageNodes:
    image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
    imagePullPolicy: PullPolicy
    mgmtInterface: string
    dataInterfaces:
      - string
    socketsToUse:
      - string
    nodesPerSocket: integer
    nodeProvisioningBudget: integer
    enableJournalDevice: boolean
    enableFormat4K: boolean
    enableBlockFormat: boolean
    enableCpuTopology: boolean
    reservedSystemCPU: string
    enableKubeletConfiguration: boolean
    ubuntuHost: boolean
    openshift:
      machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
    tolerations:
      - Toleration
    containerResources: ResourceRequirements
    initContainerResources: ResourceRequirements
  backup:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
    bucket: string
    region: string
    credentialsSecretRef: LocalObjectReference
  disableDataRealignment: boolean
  enableVolumeAutoPlacement: boolean
  volumeMigrationSettings:
    rebalancerImage: string
    dataRealignment:
      interval: Duration
      minMoves: integer
  volumeAutoPlacement:
    disableMigration: boolean
    evaluationInterval: Duration
    imbalanceThreshold: integer
    minHotColdDifferencePct: integer
    defaultCoolDownSeconds: integer
    maxVolumeMigrationsPerCycle: integer
    storageNodeCandidateCount: integer
    metricsBackend: MetricsBackend
    prometheusURL: string
    enableLatencyBenchmark: boolean
    latencyBenchmarkInterval: Duration
    baselineStrategy: BaselineStrategy
    baselineWindow: Duration
    baselineColdStart: BaselineColdStartPolicy
    baselineMinSamples: integer
    baselineOutlierK: float
    iopsWeight: float
    throughputWeight: float
status:
  phase: StorageClusterPhase
  step: KubeSnapshot
  uuid: string
  clusterName: string
  nqn: string
  erasureCodingScheme: string
  status: string
  configured: boolean
  rebalancing: boolean
  maxFaultTolerance: integer
  maxConcurrentWorkerRestarts: integer
  volumeMoveGeneration: integer
  realignedGeneration: integer
  lastDataRealignmentAt: Time
  tasks:
    - id: string
      type: string
      status: string
      retry: integer
  provisioningSlots:
    - worker: string
      node: string
      takenAt: Time
  failureDomains:
    - name: string
      index: integer
  activeOpsRef: string
  rebalancingMetrics:
    avgDeviationPct: float
    maxDeviationPct: float
    hottestNodeUUID: string
    coolestNodeUUID: string
    imbalancePercent: float
    lastEvaluatedAt: Time
    lastMigrationAt: Time
    nodeMetrics:
      - nodeUUID: string
        latencyDeviationPct: float
        volumeCount: integer
        lastUpdated: Time
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageCluster
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageClusterSpec
status StorageClusterStatus

StorageClusterDeviceClass

Underlying type: string

StorageClusterDeviceClass is the class of backend storage a cluster is built out of. The values are the two classes simplyblock accepts, spelled as the standards that name them are.

Validation: - Enum: [NVMe LogicalBlock]

Appears in: - StorageClusterSpec

Field Description
NVMe StorageClusterDeviceClassNVMe is a cluster whose nodes hand over NVMe
devices, named by PCI address.
LogicalBlock StorageClusterDeviceClassLogicalBlock is a cluster whose nodes hand over
logical block devices, named by path. The backend accepts them from 26.4.

StorageClusterOps

StorageClusterOps is a single operation performed against one StorageCluster. It runs to a terminal phase and stays afterward as the audit record of what was done, to which cluster, with which parameters, and how it ended. Only one operation acts on a cluster at a time; a second is admitted, waits at Pending, and runs when the lock frees.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageClusterOps
metadata:
  name: string
spec:
  clusterRef: string
  action: StorageClusterOpsAction
  abort: boolean
  cancelTask:
    taskID: string
  rollingRestart:
    refreshSNodeAPI: boolean
status:
  phase: StorageClusterOpsPhase
  step: KubeSnapshot
  message: string
  rollingRestart:
    nodes:
      - string
    nodeIndex: integer
    skipped:
      - string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageClusterOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageClusterOpsSpec
status StorageClusterOpsStatus

StorageClusterOpsAction

Underlying type: string

StorageClusterOpsAction is the operation a StorageClusterOps performs. The values are PascalCase, as every enum in the group is; v1alpha1 spelled them lowercase and hyphenated, and the conversion maps between the two.

Validation: - Enum: [Activate Expand Shutdown Start Restart RollingRestart CancelTask]

Appears in: - StorageClusterOpsSpec

Field Description
Activate
Expand
Shutdown
Start
Restart
RollingRestart
CancelTask StorageClusterOpsActionCancelTask is the one action whose target is
inside the cluster: it names a task of the cluster's status.tasks by its
control-plane ID.

StorageClusterOpsPhase

Underlying type: string

StorageClusterOpsPhase is the operation's own progress. Aborted is terminal and distinct from Failed, because a canceled operation did not go wrong.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - StorageClusterOpsStatus

Field Description
Pending StorageClusterOpsPhasePending: the operation holds no lock and has
issued nothing. It is both where an operation starts and where it waits
behind another one's lock.
Running StorageClusterOpsPhaseRunning: the operation holds the lock and its
first side effect may have been issued.
Succeeded
Failed
Aborted StorageClusterOpsPhaseAborted: the operation was called off. A rolling
restart holding on a degraded cluster is the one operation an
administrator has a reason to stop, and stopping it is not a failure.

StorageClusterOpsSpec

StorageClusterOpsSpec is one operation to perform against one StorageCluster.

Appears in: - StorageClusterOps

Example:

clusterRef: string
action: StorageClusterOpsAction
abort: boolean
cancelTask:
  taskID: string
rollingRestart:
  refreshSNodeAPI: boolean
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster this operation acts on. The operation
never owns its target, because deleting the record of an operation must
not delete the cluster it operated on.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Required: {}
action StorageClusterOpsAction Action is the operation to perform. Enum: [Activate Expand Shutdown Start Restart RollingRestart CancelTask]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind.
Whether an abort is expressible from the current step is declared by that
action's graph rather than checked here.
Optional: {}
cancelTask CancelTaskSpec CancelTask parameterizes action CancelTask and is ignored by the others. Optional: {}
rollingRestart RollingRestartSpec RollingRestart parameterizes action RollingRestart and is ignored by the
others.
Optional: {}

StorageClusterOpsStatus

StorageClusterOpsStatus is the observed state of one cluster operation.

Appears in: - StorageClusterOps

Example:

phase: StorageClusterOpsPhase
step: KubeSnapshot
message: string
rollingRestart:
  nodes:
    - string
  nodeIndex: integer
  skipped:
    - string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase StorageClusterOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. The value is
one of the steps the running action declares.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
rollingRestart RollingRestartStatus RollingRestart is the walk's position, set only for action
RollingRestart.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

StorageClusterPhase

Underlying type: string

StorageClusterPhase is where the operator has got to with this cluster. The first two values are the operator's own creation path; the rest are its reading of the lifecycle status.status carries in the control plane's own spelling.

Validation: - Enum: [Pending Creating Provisioning Activating Online Shrinking Rebalancing Degraded Unavailable Suspended]

Appears in: - StorageClusterStatus

Field Description
Pending StorageClusterPhasePending: the object exists and nothing has been
claimed for it yet.
Creating StorageClusterPhaseCreating: the creation machine is running.
Provisioning StorageClusterPhaseProvisioning: the cluster exists in the control plane
and is being built up — its first nodes are joining, or an expansion is
adding more. It is not serving and there is nothing wrong with it, which
is the distinction Unavailable cannot carry.
Activating StorageClusterPhaseActivating: the control plane is activating the
cluster.
It is a phase of its own rather than part of Provisioning because it is
not only the last step of a deployment: an expansion ends in one, and so
does recovering from a suspension, long after anything was being built.
Online StorageClusterPhaseOnline: the control plane reports the cluster active
and serving.
Shrinking StorageClusterPhaseShrinking: serving, and removing at least one of its
nodes. It replaces Rebalancing, Online, and Degraded for as long as the
control plane reports a removal in progress, because a removal moves the
departing node's data onto its peers and degrades the cluster while it
does, and the removal is the cause of both. It never replaces a phase
that is not serving.
Rebalancing StorageClusterPhaseRebalancing: serving, and moving data between its
nodes or devices. It replaces Online and Degraded for as long as the
control plane reports a rebalance running, because a rebalance is what
makes most operations on the cluster unavailable and the phase is the one
column a watch shows. It never replaces a phase that is not serving.
status.status keeps the control plane's own word beside it, which is how
a rebalance on a degraded cluster is told from one on an active cluster.
Degraded StorageClusterPhaseDegraded: serving, with less than the redundancy it
was built for.
Unavailable StorageClusterPhaseUnavailable: not serving, and not because anybody
asked.
It is what is left once the statuses that mean something has been asked
for are read as themselves, which is what keeps it worth reporting: a
cluster in this phase is one whose status this operator has no reading
for, rather than every cluster that is not currently serving.
Suspended StorageClusterPhaseSuspended: shut down deliberately, which is where a
Shutdown operation leaves it.

StorageClusterSpec

StorageClusterSpec is the desired state of one simplyblock backend cluster.

Appears in: - StorageCluster

Example:

maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
stripe:
  dataChunks: integer
  parityChunks: integer
fabricType: string
clientDataIfname: string
nvmfBasePort: integer
rpcBasePort: integer
snodeApiPort: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
enableChecksumValidation: boolean
enableAtomicity4K: boolean
deviceClass: StorageClusterDeviceClass
kms:
  vault:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
warningThreshold:
  capacity: integer
  provisionedCapacity: integer
criticalThreshold:
  capacity: integer
  provisionedCapacity: integer
maxConcurrentWorkerRestarts: integer
storageNodes:
  image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
  imagePullPolicy: PullPolicy
  mgmtInterface: string
  dataInterfaces:
    - string
  socketsToUse:
    - string
  nodesPerSocket: integer
  nodeProvisioningBudget: integer
  enableJournalDevice: boolean
  enableFormat4K: boolean
  enableBlockFormat: boolean
  enableCpuTopology: boolean
  reservedSystemCPU: string
  enableKubeletConfiguration: boolean
  ubuntuHost: boolean
  openshift:
    machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
  tolerations:
    - Toleration
  containerResources: ResourceRequirements
  initContainerResources: ResourceRequirements
backup:
  endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  bucket: string
  region: string
  credentialsSecretRef: LocalObjectReference
disableDataRealignment: boolean
enableVolumeAutoPlacement: boolean
volumeMigrationSettings:
  rebalancerImage: string
  dataRealignment:
    interval: Duration
    minMoves: integer
volumeAutoPlacement:
  disableMigration: boolean
  evaluationInterval: Duration
  imbalanceThreshold: integer
  minHotColdDifferencePct: integer
  defaultCoolDownSeconds: integer
  maxVolumeMigrationsPerCycle: integer
  storageNodeCandidateCount: integer
  metricsBackend: MetricsBackend
  prometheusURL: string
  enableLatencyBenchmark: boolean
  latencyBenchmarkInterval: Duration
  baselineStrategy: BaselineStrategy
  baselineWindow: Duration
  baselineColdStart: BaselineColdStartPolicy
  baselineMinSamples: integer
  baselineOutlierK: float
  iopsWeight: float
  throughputWeight: float
Field Description Default Validation
maxSubsystemCount integer MaxSubsystemCount is the maximum number of NVMe-oF subsystems per storage
node. It is the cluster's and no node carries a copy: every node's
configuration is generated from this field, so changing it reaches the
nodes that already exist as each of them next restarts.
Required: it sizes huge pages, and a node that receives no value fails
config generation outright rather than falling back to a default.
Maximum: 75
Minimum: 10
Required: {}
vcpuCount integer VCPUCount is the number of vCPUs allocated to SPDK on each storage node,
as an explicit core count rather than a percentage. Unlike
MaxSubsystemCount it is stamped onto a node when the node is created,
because it describes the host that node runs on. Required: the core
layout it produces must match across the cluster in steady state, so it
is stated rather than left to a per-node heuristic.
The floor is 4 rather than a hardware limit: a node must carry one core
beyond this budget for the system, and the control plane's core layout
assigns no NVMe-oF poller core at all for a 2-vCPU budget.
Minimum: 4
Required: {}
minHugePagesSize string MinHugePagesSize is the smallest huge-page allocation each storage node
makes, as a size string such as 100G or 1T, where a bare number is
gigabytes. It is a floor and not a limit: the effective allocation is the
larger of this value and the minimum the node's device and subsystem
count requires, so simplyblock takes more when it needs more. Omitted,
the computed minimum is used.
Optional: {}
stripe StripeSpec Stripe is the erasure-coding layout every volume in the cluster is
written with. It describes on-disk layout, so it cannot change under a
live cluster.
Optional: {}
fabricType string FabricType is the storage fabric the cluster serves volumes over. It
describes on-wire layout, so it cannot change under a live cluster.
Optional: {}
clientDataIfname string ClientDataIfname is the network interface clients reach the data plane
on. It is mutable: the control plane reads it on every connect rather than
once at cluster-add, so an edit moves the next attach onto the named
interface.
Optional: {}
nvmfBasePort integer NvmfBasePort is the base of the NVMe-oF port range every node binds.
The default is the control plane's own, which is what it applies to a
cluster that sends none, so the field states the number the cluster
actually runs with rather than leaving a reader to know the backend.
4420 Maximum: 65535
Minimum: 1024
Optional: {}
rpcBasePort integer RpcBasePort is the base of the RPC port range every node binds. Its
default is the control plane's, as NvmfBasePort's is.
8080 Maximum: 65535
Minimum: 1024
Optional: {}
snodeApiPort integer SnodeApiPort is the port each node's storage-node API listens on. Its
default is the control plane's, as NvmfBasePort's is.
50001 Maximum: 65535
Minimum: 1024
Optional: {}
enableFailureDomains boolean EnableFailureDomains opts the cluster into failure-domain mode, where
every node must label the fault group it belongs to so the control plane
can spread erasure-coding chunks across independent ones.
Optional: {}
enableNodeAffinity boolean EnableNodeAffinity has the data plane serve an erasure-coded volume's I/O
from the local node's own devices where it can, before crossing the
network.
It is not Kubernetes affinity, and the name is the one place this API
invites that reading: nothing about it schedules a pod, labels a worker,
or places a volume's primary node. The control plane carries it into the
cluster map it pushes to each node, where it sets the local node's index,
and what changes is which copy of a chunk is read.
Co-locating a workload with the primary node of its volume is a separate
mechanism and is not configured here.
Optional: {}
enableChecksumValidation boolean EnableChecksumValidation turns on inline CRC validation of every I/O, for
silent-data-error protection. The backend bakes the checksum method into
each device at cluster-create time and never re-applies it, so it cannot
change under a live cluster.
false Optional: {}
enableAtomicity4K boolean EnableAtomicity4K enforces 4K write atomicity on devices that report a
smaller logical block size, which lets checksum fallback mode run on them
despite the data plane's usual 4K minimum.
It is an enforcement rather than a reading, and that is what it is for.
A device may complete a 4K write whole across a power failure and have no
way to say so: a SATA drive presenting 512-byte logical blocks over a 4K
physical sector reports 512 and nothing else, and a kernel older than 6.11
publishes no atomic write attributes at all, so the fleet it runs on
cannot be asked. Where the hardware can answer, the storage node's report
carries what it said; where it cannot, this is how an administrator states
what they know and the cluster proceeds on it.
Which is why it is the setting that loses data when it is wrong. An
enforced guarantee the hardware does not keep is a torn write under a
checksum that disagrees with it, so it is approved against the devices'
own report where one exists.
It means nothing unless EnableChecksumValidation is set, and it cannot
change under a live cluster.
false Optional: {}
deviceClass StorageClusterDeviceClass DeviceClass is the class of backend storage every node in this cluster
hands over: NVMe devices named by PCI address, or logical block devices
named by path. A cluster is built out of one of them, because an
erasure-coding stripe placed across both is written and rebuilt at the
slower class's rate. It describes on-disk layout, so it cannot change
under a live cluster, and it defaults to NVMe because that is the only
class the backend accepted before 26.4.
NVMe Enum: [NVMe LogicalBlock]
kms KMSSpec KMS selects where the cluster stores volume encryption keys. Switching
providers on a live cluster is at least as unsupportable as changing one
provider's endpoint, which is why the whole block is immutable rather
than its members.
Optional: {}
warningThreshold CapacityThresholdSpec WarningThreshold is the capacity level at which the cluster warns. Optional: {}
criticalThreshold CapacityThresholdSpec CriticalThreshold is the capacity level at which the cluster alarms. Optional: {}
maxConcurrentWorkerRestarts integer MaxConcurrentWorkerRestarts caps how many Kubernetes workers the operator
may drain and restart at once. The effective value is the smaller of this
and status.maxFaultTolerance, published as
status.maxConcurrentWorkerRestarts so tooling reads one authoritative
number rather than recomputing it.
1 Minimum: 1
Optional: {}
storageNodes StorageNodesSpec StorageNodes is the Kubernetes workload the cluster's storage nodes run
as, and the cluster owns every object in it by controller reference: a
cluster deleted takes its DaemonSet, Services, certificate, and per-node
ConfigMap with it. One workload serves the whole cluster, because growth is
nodes rather than sets and what differs between hardware generations is per
node already.
Optional: {}
backup BackupStoreSpec Backup is the S3 location this cluster's backups live in, and it is both
the target copies are written to and the inventory the operator walks to
produce StorageBackup objects. Mutable: a cluster that has never had a
store gains one by acquiring the field, and every backup already in that
bucket becomes visible.
Optional: {}
disableDataRealignment boolean DisableDataRealignment turns off the post-migration data realignment,
which runs by default. It is spelled as a disable because the behavior it
governs is on: realignment restores the fault-tolerance and node-affinity
guarantees every volume move invalidates, so a cluster that says nothing
gets them back rather than silently accumulating unaligned structures.
Turning it off is for a cluster migrating continuously, where a run that
blocks migrations for tens of minutes costs more than the delay in
realigning; volumeMigrationSettings.dataRealignment.minMoves is the
gentler answer to the same problem.
It is a field of the spec rather than of the block it governs, because
volumeMigrationSettings.dataRealignment.disableDataRealignment says the
same word twice. There is no DisableVolumeMigration beside it: migration
cannot be turned off, since a drain, a rebalance, and a device
replacement are all performed by moving volumes.
Optional: {}
enableVolumeAutoPlacement boolean EnableVolumeAutoPlacement turns on automatic, latency-driven rebalancing. Optional: {}
volumeMigrationSettings VolumeMigrationSettings VolumeMigrationSettings controls how volume migration and the
post-migration realignment behave, not whether they happen. It is
separate from volumeAutoPlacement because realignment applies to every
volume move, whatever asked for it.
Optional: {}
volumeAutoPlacement VolumeAutoPlacementSettings VolumeAutoPlacement configures automatic, latency-driven rebalancing. Optional: {}

StorageClusterStatus

Appears in: - StorageCluster

Example:

phase: StorageClusterPhase
step: KubeSnapshot
uuid: string
clusterName: string
nqn: string
erasureCodingScheme: string
status: string
configured: boolean
rebalancing: boolean
maxFaultTolerance: integer
maxConcurrentWorkerRestarts: integer
volumeMoveGeneration: integer
realignedGeneration: integer
lastDataRealignmentAt: Time
tasks:
  - id: string
    type: string
    status: string
    retry: integer
provisioningSlots:
  - worker: string
    node: string
    takenAt: Time
failureDomains:
  - name: string
    index: integer
activeOpsRef: string
rebalancingMetrics:
  avgDeviationPct: float
  maxDeviationPct: float
  hottestNodeUUID: string
  coolestNodeUUID: string
  imbalancePercent: float
  lastEvaluatedAt: Time
  lastMigrationAt: Time
  nodeMetrics:
    - nodeUUID: string
      latencyDeviationPct: float
      volumeCount: integer
      lastUpdated: Time
message: string
observedGeneration: integer
Field Description Default Validation
phase StorageClusterPhase Phase is the operator's own view of this cluster. Enum: [Pending Creating Provisioning Activating Online Shrinking Rebalancing Degraded Unavailable Suspended]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the creation machine. The value is one of the
steps that machine declares.
Optional: {}
uuid string UUID is the backend cluster UUID. Empty means the cluster has not been
created or adopted, and non-empty means steady state.
Optional: {}
clusterName string ClusterName is the resolved backend name. Optional: {}
nqn string NQN is the cluster subsystem qualified name. Optional: {}
erasureCodingScheme string ErasureCodingScheme is the active layout, rendered as ndcs, an x, and
npcs: a two-plus-one cluster reads 2x1.
Optional: {}
status string Status is the lifecycle the control plane reports, and its values are the
control plane's, which is why they are neither PascalCase nor constrained
by an Enum here.
Optional: {}
configured boolean Configured records that initial setup completed. Optional: {}
rebalancing boolean Rebalancing is the control plane's report that a rebalance is in
progress, which is one of the two conditions that hold a node operation.
Optional: {}
maxFaultTolerance integer MaxFaultTolerance is how many nodes may be simultaneously offline without
violating redundancy, as the control plane reports it.
Optional: {}
maxConcurrentWorkerRestarts integer MaxConcurrentWorkerRestarts is the effective limit, the smaller of
spec.maxConcurrentWorkerRestarts and maxFaultTolerance.
Optional: {}
volumeMoveGeneration integer VolumeMoveGeneration is incremented by every migration reaching its
successful terminal phase and by nothing else, so it only grows.
Optional: {}
realignedGeneration integer RealignedGeneration is the generation the last successfully requested
realignment covers. A realignment is outstanding while
volumeMoveGeneration exceeds it. The value recorded is the one read
before the request was sent, which is what that realignment can actually
account for: a migration completing while the request is in flight raises
volumeMoveGeneration past it and correctly leaves another realignment
outstanding.
Optional: {}
lastDataRealignmentAt Time ⧉ LastDataRealignmentAt is when a realignment was last requested, and it is
what the configured interval spaces requests against.
Optional: {}
tasks ClusterTask array Tasks are the control plane's running and pending jobs, capped at twenty
and in the order the control plane reports them, which is not newest
first. Completed and canceled tasks are not here: they leave the list and
become events, so the length tracks concurrency rather than history.
MaxItems: 20
Optional: {}
provisioningSlots ProvisioningSlot array ProvisioningSlots are the workers whose node add is outstanding. The list
is the metadata of the Provisioning phase, and it is also the mutex that
caps concurrent adds at spec.storageNodes.nodeProvisioningBudget.
It is one list on one object because that is what makes taking a slot
atomic. A node takes one with an optimistic-locked patch of this field, so
exactly one node wins a given resourceVersion and every other is told to
count again. A slot recorded per node could not do that: six objects carry
six resourceVersions, and two nodes reading a cold cache would both see the
same one free and both take it.
A worker rather than an object is what holds a slot, because one POST adds
every socket of a worker and a two-socket host must consume one slot.
MaxItems: 64
Optional: {}
failureDomains FailureDomainIndex array FailureDomains maps each failure-domain label the cluster's nodes declare
(StorageNode.spec.config.failureDomain) to the integer the control plane
identifies that domain by. A deployment adds an entry for every label it
introduces, both when it creates the cluster and when it grows one, and
never changes or removes an entry: the control plane has already placed
data by that index. A label that is a number keeps that number as its
index when the number is free. A cluster holds at most 256 failure
domains, and a deployment that would exceed that is refused before it is
approved.
MaxItems: 256
Optional: {}
activeOpsRef string ActiveOpsRef names the StorageClusterOps currently allowed to operate on
this cluster. Empty when none is running.
Optional: {}
rebalancingMetrics RebalancingMetrics RebalancingMetrics is written by the auto-rebalancer each evaluation
cycle.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the cluster moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

StorageDevice

StorageDevice is one storage backend device belonging to one storage node. It is the bottom of the ownership spine and the narrowest thing an operation can target.

Objects are created by the operator from what the control plane reports and are never written by a user: the device's existence follows from the node having it, and which devices a node uses is decided in the node's own spec.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageDevice
metadata:
  name: string
spec:
  nodeRef: string
  deviceID: string
status:
  phase: StorageDevicePhase
  deviceStatus: string
  role: StorageDeviceRole
  capacity:
    totalBytes: integer
  hardware:
    pciAddress: string
    serialNumber: string
    model: string
    nvmeController: string
  clusterID: string
  nodeID: string
  activeOpsRef: string
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageDevice
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata. Optional: {}
spec StorageDeviceSpec spec identifies the mirrored control-plane device Required: {}
status StorageDeviceStatus status defines the observed state of the device Optional: {}

StorageDeviceOps

StorageDeviceOps is a single operation performed against one StorageDevice.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageDeviceOps
metadata:
  name: string
spec:
  deviceRef: string
  action: StorageDeviceOpsAction
  abort: boolean
status:
  phase: StorageDeviceOpsPhase
  step: KubeSnapshot
  deviceStatusBefore: string
  message: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageDeviceOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageDeviceOpsSpec
status StorageDeviceOpsStatus

StorageDeviceOpsAction

Underlying type: string

StorageDeviceOpsAction is the operation a StorageDeviceOps performs.

There is no Add: a device that appears is discovered. There is no bare Remove either: a removal is a step of Replace and of Migrate, and taking a device out of the data path without replacing it is Fail.

Validation: - Enum: [Restart Fail]

Appears in: - StorageDeviceOpsSpec

Field Description
Restart StorageDeviceOpsActionRestart is the action the kind exists for:
recycling one device rather than its node.
Fail StorageDeviceOpsActionFail declares a device untrustworthy and takes it
out of the data path for good, so the cluster rebuilds the redundancy it
held elsewhere and stops reading from it.
It is two calls rather than one: the control plane refuses to fail a
device that is still serving, so the device is removed and then failed.
The removal alone is reversible and the failure is not, which is why the
graph splits them and declares the abort edge on the first.
SelfTest These three actions are declared but not accepted: the control plane has
no verb for them yet, so an object naming one is refused at admission
rather than created and failed.
Replace
Migrate

StorageDeviceOpsPhase

Underlying type: string

StorageDeviceOpsPhase is the operation's own progress.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - StorageDeviceOpsStatus

Field Description
Pending
Running
Succeeded
Failed
Aborted

StorageDeviceOpsSpec

StorageDeviceOpsSpec is one operation to perform against one StorageDevice.

Appears in: - StorageDeviceOps

Example:

deviceRef: string
action: StorageDeviceOpsAction
abort: boolean
Field Description Default Validation
deviceRef string DeviceRef names the StorageDevice this operation acts on, in this
operation's own namespace. The operation never owns its target, because
deleting the record of an operation must not delete the device record it
operated on.
MaxLength: 253
Required: {}
action StorageDeviceOpsAction Action is the operation to perform. Enum: [Restart Fail]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind.
Each action can be aborted before it has issued anything and not after: a
restart the control plane has accepted is one nothing can recall, and a
device a failure has already removed is one this operator has no call to
put back. The graph declares where the edge exists rather than this field
promising one.
Optional: {}

StorageDeviceOpsStatus

StorageDeviceOpsStatus is the observed state of one device operation.

Appears in: - StorageDeviceOps

Example:

phase: StorageDeviceOpsPhase
step: KubeSnapshot
deviceStatusBefore: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase StorageDeviceOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. It is
persisted before the side effect that step performs.
Optional: {}
deviceStatusBefore string DeviceStatusBefore is what the control plane reported the device's status
to be when the operation took its lock, so a wait can tell the device
coming back from its never having gone.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

StorageDevicePhase

Underlying type: string

StorageDevicePhase is the operator's own view of a device. Degraded and Failed are deliberately distinct: a degraded device is serving and should not be, while a failed one is not serving and the cluster is running with less redundancy than it thinks until it is replaced.

Validation: - Enum: [Online Degraded Unknown Removed Failed Migrated]

Appears in: - StorageDeviceStatus

Field Description
Online
Degraded
Unknown StorageDevicePhaseUnknown is a device whose node cannot be reached, so its
state is not observable rather than bad. A terminal phase is not
overwritten by it.
Removed
Failed
Migrated StorageDevicePhaseMigrated is a device taken out of service whose data the
control plane has rebuilt onto the node's peers, so the cluster carries no
less redundancy for it. It is where the devices of a removed node end up,
and like Failed and Removed it is terminal.

StorageDeviceRole

Underlying type: string

StorageDeviceRole is what the device carries. It is decided when the node is created, by spec.storageNodes.enableJournalDevice, and it is what makes one device's failure worse than another's.

Validation: - Enum: [Storage Journal]

Appears in: - StorageDeviceStatus

Field Description
Storage
Journal

StorageDeviceSpec

StorageDeviceSpec identifies the device this object reports on, and carries nothing else. Everything a user could change about a device is on the node, so a spec field here would be a second place to set the same thing.

Appears in: - StorageDevice

Example:

nodeRef: string
deviceID: string
Field Description Default Validation
nodeRef string NodeRef names the StorageNode this device belongs to. The node owns this
object by controller reference, so deleting the node deletes its devices.
Required: {}
deviceID string DeviceID is the control plane's identifier for the device. With NodeRef it
is the whole of this object's identity.
Required: {}

StorageDeviceStatus

StorageDeviceStatus is everything observed about the device.

Appears in: - StorageDevice

Example:

phase: StorageDevicePhase
deviceStatus: string
role: StorageDeviceRole
capacity:
  totalBytes: integer
hardware:
  pciAddress: string
  serialNumber: string
  model: string
  nvmeController: string
clusterID: string
nodeID: string
activeOpsRef: string
message: string
observedGeneration: integer
Field Description Default Validation
phase StorageDevicePhase Phase is the operator's own view of the device. Enum: [Online Degraded Unknown Removed Failed Migrated]
Optional: {}
deviceStatus string DeviceStatus is the control plane's own string, in the control plane's
spelling, which is why it carries no Enum here.
Optional: {}
role StorageDeviceRole Role is what the device carries. Enum: [Storage Journal]
Optional: {}
capacity DeviceCapacity Capacity is how big the device is. What it holds is served as
StorageDeviceMetrics instead.
Optional: {}
hardware DeviceHardware Hardware identifies the part. Optional: {}
clusterID string ClusterID and NodeID are the backend ids of the stream this device was
last observed on. They are recorded because an object outliving its device
has to say which scope's absence would be authoritative before the mirror
may delete it; the object's own spec names Kubernetes objects rather than
backend ones, so it cannot answer that on its own.
Optional: {}
nodeID string Optional: {}
activeOpsRef string ActiveOpsRef names the StorageDeviceOps currently allowed to act on this
device, and is empty when none is. It is the operation lock every entity
of this group carries, and until StorageDeviceOps exists nothing takes it,
so the field is present and always empty. Declaring it now is what lets a
reader tell "no operation is running" from "this kind cannot say," and it
keeps the lock out of the change that introduces the operations.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the device moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from. On this kind it moves at most once, since every spec field is fixed
when the object is created.
Optional: {}

StorageNode

StorageNode is one backend storage node: one SPDK process bound to one NUMA socket of one Kubernetes worker. One object exists per (workerNode, slot) pair, owned by the StorageCluster it belongs to.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageNode
metadata:
  name: string
spec:
  clusterRef: string
  nodeSet: string
  workerNode: string
  socketId: string
  nodeIndex: integer
  slot: integer
  config:
    sizing:
      vcpuCount: integer
      minHugePagesSize: string
    spdkImage: string
    spdkImagePullPolicy: PullPolicy
    spdkProxyImage: string
    spdkProxyImagePullPolicy: PullPolicy
    spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
    reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
    journalManager:
      count: integer
      percentPerDevice: integer
    deviceNames:
      - '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
    pcieAllowList:
      - string
    pcieDenyList:
      - string
    pcieModel: string
    driveSizeRange: string
    failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
    expand: boolean
status:
  phase: StorageNodePhase
  step: KubeSnapshot
  uuid: string
  nodeAddTaskID: string
  status: string
  health: boolean
  hostname: string
  uptime: string
  resources:
    cpu: integer
    memory: string
    volumes: integer
    devices:
      online: integer
      total: integer
    capacity:
      totalBytes: integer
      usedBytes: integer
      sampledAt: Time
  ports:
    management: string
    nvmeof: integer
    lvol: integer
    rpc: integer
  failureDomain: string
  activeOpsRef: string
  latencyMetrics:
    nodeUUID: string
    baselineP50NS: integer
    baselineP99NS: integer
    baselineMeasuredAt: Time
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageNode
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageNodeSpec
status StorageNodeStatus

StorageNodeCapacity

StorageNodeCapacity is a node's storage occupancy, as the control plane last measured it.

It carries the same two numbers as a device's capacity, because a node's is the sum of its devices' and a reader comparing the two should not have to reconcile different shapes. It is written only when the reading has moved materially: a sample that changed by a few blocks is not worth an etcd write, and writing every sample would make the reconciler retrigger itself on its own status update.

Appears in: - StorageNodeResources

Example:

totalBytes: integer
usedBytes: integer
sampledAt: Time
Field Description Default Validation
totalBytes integer TotalBytes is the storage the node's devices provide. Minimum: 0
Optional: {}
usedBytes integer UsedBytes is what they currently hold. Minimum: 0
Optional: {}
sampledAt Time ⧉ SampledAt is when the control plane took the reading. It is not when the
object was written, and it may be considerably older if metrics collection
has stopped.
Optional: {}

StorageNodeConfig

StorageNodeConfig is a storage node's complete configuration, copied from the ClusterDeploymentConfig entry that produced the node. It is a copy rather than a projection, because that document is ephemeral and nothing reads it once the node exists.

Most of it is immutable: by marker where a field has no legitimate writer, and by the validating webhook where it has exactly one.

Appears in: - StorageNodeSpec

Example:

sizing:
  vcpuCount: integer
  minHugePagesSize: string
spdkImage: string
spdkImagePullPolicy: PullPolicy
spdkProxyImage: string
spdkProxyImagePullPolicy: PullPolicy
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
  count: integer
  percentPerDevice: integer
deviceNames:
  - '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
pcieAllowList:
  - string
pcieDenyList:
  - string
pcieModel: string
driveSizeRange: string
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
expand: boolean
Field Description Default Validation
sizing StorageNodeSizing Sizing is what this node's huge pages and core layout were sized from.
Writable by the operator alone.
Required: {}
spdkImage string SpdkImage overrides the SPDK image the control plane starts for this node,
which is what makes a phased image rollout expressible per node.
Optional: {}
spdkImagePullPolicy PullPolicy ⧉ SpdkImagePullPolicy controls when that image is pulled, and defaults to
Always because the images this product ships are moving tags.
The control plane starts the SPDK pod, not the operator, and its
spdk_process_start takes no pull policy: the pod template it renders writes
Always itself. So a node states the policy here and the node-add call does
not yet carry it, which is a gap the control plane closes rather than this
kind. Stating anything but Always is therefore recorded and not yet obeyed.
Always Enum: [Always Never IfNotPresent]
Optional: {}
spdkProxyImage string SpdkProxyImage overrides the SPDK proxy image for this node. Optional: {}
spdkProxyImagePullPolicy PullPolicy ⧉ SpdkProxyImagePullPolicy controls when that image is pulled. It is stated
apart from SpdkImagePullPolicy because a node may pin the proxy and follow
the SPDK image, and it carries the same not-yet-spent caveat: the template
the control plane renders writes Always for both containers.
Always Enum: [Always Never IfNotPresent]
Optional: {}
spdkSystemMemory string SpdkSystemMemory is the memory the control plane starts this node's SPDK
with, as a size string such as 4G or 512M. It can be raised, because a
node whose device count grew legitimately needs more, and who may raise it
is decided at admission.
Pattern: ^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$
Optional: {}
reservedSystemCPU string ReservedSystemCPU is the CPU set held back from SPDK for the system, as a
core list such as 0,1 or 0-3.
It is per node and not per cluster because it names core ids: 0,1 on a
sixteen-core worker and 0,1 on a ninety-six-core worker are different
fractions of the machine, and a fleet whose groups differ in core count
has no one list that is right for all of them.
StorageCluster.spec.storageNodes.reservedSystemCPU is the fleet's value,
which the pod carries as an environment variable, and this overrides it
for the node that states it.
On OpenShift the value reaches the kubelet through a KubeletConfig for
the machine config pool rather than through the node alone, so nodes
sharing a pool that disagree are writing over one another's pool
configuration. It carries no immutability marker, because the CPUs a
machine holds back are a tuning decision rather than a layout one, and
the webhook that guards spec.config is what decides who may retune
them.
MaxLength: 63
Pattern: ^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$
Optional: {}
journalManager JournalManagerSpec JournalManager tunes the journal manager count and per-device capacity
share for this node. Immutable: both are on-disk layout, fixed when the
devices were partitioned.
Optional: {}
deviceNames string array DeviceNames names the devices to use. An entry is a PCI address
("0000:5e:00.0") or a device path ("/dev/sdb,") which are the two classes
simplyblock accepts as backend storage, and a bare name ("nvme0n1") is read
as a path under /dev. One list carries both spellings, and every entry is of
the class its cluster declares in StorageCluster.spec.deviceClass: a list
mixing the two, or naming the class the cluster is not, is rejected by the
StorageNode validating webhook. Set explicitly, it overrides every filter
below. Immutable: it selects which physical devices the node owns.
items:Pattern: ^([0-9a-fA-F]\{4\}:[0-9a-fA-F]\{2\}:[0-9a-fA-F]\{2\}\.[0-9a-fA-F]\|/dev/[a-zA-Z0-9._/-]+\|[a-zA-Z0-9._-]+)$
Optional: {}
pcieAllowList string array PcieAllowList selects devices by PCI address. It is the one device field a
migration writes, merging spec.migrate.newSsdPcie into it so devices added
on the target host survive a later rebuild, so it is guarded by the
StorageNode validating webhook rather than by a marker. This and the two
PCI filters below belong to an NVMe cluster: the webhook rejects them on a
cluster whose deviceClass is LogicalBlock, because a logical block device
has no PCI address to match.
Optional: {}
pcieDenyList string array PcieDenyList excludes devices by PCI address. Optional: {}
pcieModel string PcieModel filters devices by PCI model string. Optional: {}
driveSizeRange string DriveSizeRange filters devices by size. Optional: {}
failureDomain string FailureDomain is the label of the fault group this node belongs to
such as rack-b, naming the physical grouping it shares with its peers rather
than indexing it. Required when the cluster has enableFailureDomains set,
and provisioning is held with a FailureDomainMissing event until it is
present. Immutable once set, which is what makes it fillable later and then
frozen: chunk placement was computed from it.
The value takes the shape of a Kubernetes label value, because that is what
it is seeded from where a cluster carries topology labels at all.
MaxLength: 63
Pattern: ^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$
Optional: {}
expand boolean Expand marks this node as an addition to an already-active cluster, which
the control plane reads as a request to rebalance onto it rather than to
treat it as part of an initial layout. Immutable once set: it describes how
the node joined rather than what it is.
Optional: {}

StorageNodeDevices

StorageNodeDevices counts the NVMe devices on a node and how many of them are online. It is a summary rather than an inventory: per-device capacity, health, and conditions belong to StorageDevice.

Neither field takes omitempty. Zero online devices is the condition worth seeing, and a field that disappears at zero would report it as nothing at all. A node the control plane has not reported on is the absent parent instead.

Appears in: - StorageNodeResources

Example:

online: integer
total: integer
Field Description Default Validation
online integer Online is how many of the node's devices the control plane reports as
usable.
Minimum: 0
total integer Total is how many devices the node has. Minimum: 0

StorageNodeOps

StorageNodeOps is a single operation performed against one StorageNode. It runs to a terminal phase and stays afterward as the audit record of what was done, to which node, with which parameters, and how it ended.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageNodeOps
metadata:
  name: string
spec:
  nodeRef: string
  action: StorageNodeOpsAction
  abort: boolean
  force: boolean
  reattachVolume: boolean
  migrate:
    targetWorkerNode: string
    newSsdPcie:
      - string
  remove:
    systemVolumeFilterRegex: string
status:
  phase: StorageNodeOpsPhase
  removal:
    nodeStatus: string
    devices:
      string: string
    lastProgressTime: Time
    prepareAttempts: integer
    lastPrepareTime: Time
  step: KubeSnapshot
  message: string
  drain:
    volumesTotal: integer
    volumesMigrated: integer
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageNodeOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageNodeOpsSpec
status StorageNodeOpsStatus

StorageNodeOpsAction

Underlying type: string

StorageNodeOpsAction is the operation a StorageNodeOps performs. Values are PascalCase, which is the casing every enum this API group defines carries; v1alpha1 spelled them lowercase, and the conversion maps between the two.

Validation: - Enum: [Shutdown Restart Suspend Resume Remove Migrate HostMaintenance]

Appears in: - StorageNodeOpsSpec

Field Description
Shutdown
Restart
Suspend
Resume
Remove
Migrate
HostMaintenance StorageNodeOpsActionHostMaintenance takes a node down deliberately so that
its Kubernetes worker can be drained and rebooted, then brings it back. The
operator raises it when it sees the worker cordoned, and a user creating
one by hand behaves identically.

StorageNodeOpsPhase

Underlying type: string

StorageNodeOpsPhase is the operation's own progress. Aborted is terminal and distinct from Failed, because a canceled operation did not go wrong.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - StorageNodeOpsStatus

Field Description
Pending StorageNodeOpsPhasePending: the operation holds no lock and has issued
nothing.
Running StorageNodeOpsPhaseRunning: it holds its node's lock and its first side
effect may have been issued.
Succeeded
Failed
Aborted StorageNodeOpsPhaseAborted: called off rather than gone wrong. A drain an
administrator stops after an hour reported as Failed would sit in the same
bucket as one the control plane rejected.

StorageNodeOpsSpec

StorageNodeOpsSpec is one operation to perform against one StorageNode.

Appears in: - StorageNodeOps

Example:

nodeRef: string
action: StorageNodeOpsAction
abort: boolean
force: boolean
reattachVolume: boolean
migrate:
  targetWorkerNode: string
  newSsdPcie:
    - string
remove:
  systemVolumeFilterRegex: string
Field Description Default Validation
nodeRef string NodeRef names the StorageNode this operation acts on. The operation never
owns its target, because deleting the record of an operation must not delete
the node it operated on.
Required: {}
action StorageNodeOpsAction Action is the operation to perform. Enum: [Shutdown Restart Suspend Resume Remove Migrate HostMaintenance]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind. It is
the only mutable field on this spec, because it is the only thing about an
operation that can legitimately be decided after it started. Whether an
abort is expressible from the current step is declared by that action's
graph rather than checked here.
Optional: {}
force boolean Force passes the control plane's force flag where the action supports it.
Migrate defaults it to true, because the control plane rejects a non-forced
restart of a node that is not already offline.
Optional: {}
reattachVolume boolean ReattachVolume asks the control plane to reattach this node's volumes as
part of a restart. Applies to Restart, Migrate, and HostMaintenance.
Optional: {}
migrate MigrateSpec Migrate parameterizes action Migrate and is ignored by the others. Optional: {}
remove RemoveSpec Remove parameterizes action Remove and is ignored by the others. Optional: {}

StorageNodeOpsStatus

StorageNodeOpsStatus is the observed state of one node operation.

Appears in: - StorageNodeOps

Example:

phase: StorageNodeOpsPhase
removal:
  nodeStatus: string
  devices:
    string: string
  lastProgressTime: Time
  prepareAttempts: integer
  lastPrepareTime: Time
step: KubeSnapshot
message: string
drain:
  volumesTotal: integer
  volumesMigrated: integer
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase StorageNodeOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
removal RemovalStatus Removal is the control plane's progress through the removal, written by
a Remove while it waits in AwaitingRemoval.
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. The value is
one of the steps the running action declares.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as the
operation moves, and never a log.
Optional: {}
drain DrainStatus Drain is the drain's progress over the node's volumes, set only for action
Remove.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

StorageNodePhase

Underlying type: string

StorageNodePhase is where the operator has got to with this node. The first two values are the operator's own provisioning path; the rest are its reading of the lifecycle status.status carries in the control plane's own spelling.

Validation: - Enum: [Pending Provisioning Online Removing Removed Offline Degraded Failed]

Appears in: - StorageNodeStatus

Field Description
Pending StorageNodePhasePending: the object exists and no slot has been claimed
for it yet.
Provisioning StorageNodePhaseProvisioning: the provisioning machine is running.
Online StorageNodePhaseOnline: the control plane reports the node online and
carrying its share.
Removing StorageNodePhaseRemoving: the control plane is removing the node, from the
removal being accepted until its devices and volumes have moved off.
Removed StorageNodePhaseRemoved: the control plane has removed the node. It is the
last phase a node reaches, and nothing brings it back into service.
Offline StorageNodePhaseOffline: out of service and reachable, which is where
Shutdown, Suspend, and a host maintenance window leave it.
Degraded StorageNodePhaseDegraded: serving with less than its devices, which is the
node-level half of what StorageDevice reports per device.
Failed StorageNodePhaseFailed: unreachable, timed out, or provisioning that will
not complete.

StorageNodePorts

StorageNodePorts groups the addresses and ports a node listens on.

Appears in: - StorageNodeStatus

Example:

management: string
nvmeof: integer
lvol: integer
rpc: integer
Field Description Default Validation
management string Management is the management IP address of the node. Optional: {}
nvmeof integer The NVMe-oF fabric port. Optional: {}
lvol integer Lvol is the logical-volume subsystem port. Optional: {}
rpc integer Rpc is the RPC and management API port. Optional: {}

StorageNodeReference

StorageNodeReference locates a StorageNode from a cluster-scoped object.

Every other reference in this API group is a bare string, which works because both objects are namespaced and a name means the same namespace. A cluster-scoped object has no namespace for a bare name to mean, and two clusters in two namespaces may each hold a node called worker-3, so this one carries both — for the same reason pv.spec.claimRef does in core Kubernetes.

Appears in: - MigrateVolumeSpec

Example:

namespace: string
name: string
Field Description Default Validation
namespace string Namespace is where the StorageNode lives, which is the namespace of the
StorageCluster that owns it.
Required: {}
name string Name is the StorageNode object's name. Required: {}

StorageNodeResources

StorageNodeResources groups the compute and storage figures the control plane reports for a node.

Appears in: - StorageNodeStatus

Example:

cpu: integer
memory: string
volumes: integer
devices:
  online: integer
  total: integer
capacity:
  totalBytes: integer
  usedBytes: integer
  sampledAt: Time
Field Description Default Validation
cpu integer CPU is the number of SPDK cores allocated to this node. Optional: {}
memory string Memory is the SPDK memory allocation the control plane reports. Optional: {}
volumes integer Volumes is the current number of logical volumes on this node. Optional: {}
devices StorageNodeDevices Devices summarizes the node's NVMe devices. Absent until the control plane
has reported, which is what tells a node that has not reported from one that
genuinely has no devices.
Optional: {}
capacity StorageNodeCapacity Capacity is how much of the node's storage is in use, summed over its
devices. It is a measurement rather than a declaration, so it is absent
until something has measured it, and it lags reality by the interval at
which the control plane's metrics are scraped.
Optional: {}

StorageNodeSizing

StorageNodeSizing is what this node's SPDK core layout and huge-page floor were sized from. It is stamped from StorageCluster.spec when the node is created and is equal across the fleet in steady state; a rolling hardware upgrade is what makes two nodes differ, and only for as long as the roll takes. The StorageNode validating webhook admits a change from the operator and rejects it from everyone else, because unmanaged divergence is what stops the control plane placing erasure-coding chunks evenly.

The cluster's maxSubsystemCount is not copied in here. It bounds how many volumes a node can serve rather than describing the host the node runs on, so it is the same for every node of a cluster and is read from the cluster when the node's configuration is generated.

Appears in: - StorageNodeConfig

Example:

vcpuCount: integer
minHugePagesSize: string
Field Description Default Validation
vcpuCount integer VCPUCount is the number of vCPUs allocated to SPDK on this node, as an
explicit core count rather than a percentage.
Minimum: 4
Required: {}
minHugePagesSize string MinHugePagesSize is the smallest huge-page allocation this node makes, as a
size string such as 100G or 1T, where a bare number is gigabytes. It is a
floor rather than a limit: the effective allocation is the larger of this
value and the minimum the node's device and subsystem count requires.
Optional: {}

StorageNodeSpec

StorageNodeSpec is the desired state of one backend storage node, meaning one SPDK process bound to one NUMA socket of one Kubernetes worker.

Appears in: - StorageNode

Example:

clusterRef: string
nodeSet: string
workerNode: string
socketId: string
nodeIndex: integer
slot: integer
config:
  sizing:
    vcpuCount: integer
    minHugePagesSize: string
  spdkImage: string
  spdkImagePullPolicy: PullPolicy
  spdkProxyImage: string
  spdkProxyImagePullPolicy: PullPolicy
  spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
  reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
  journalManager:
    count: integer
    percentPerDevice: integer
  deviceNames:
    - '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
  pcieAllowList:
    - string
  pcieDenyList:
    - string
  pcieModel: string
  driveSizeRange: string
  failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
  expand: boolean
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster this node belongs to. The cluster also
owns this object by controller reference, so deleting the cluster deletes
its nodes.
Bounded at what a StorageCluster name may be, since a longer value names
nothing that can exist.
MaxLength: 63
Required: {}
nodeSet string NodeSet is the name of the group in ClusterDeploymentConfig.nodeSets[] this
node was declared under. It is a label rather than a reference: nothing is
fetched by it, and it exists so that a node can be traced back to the
document that produced it.
Optional: {}
workerNode string WorkerNode is the Kubernetes worker hostname this node runs on. It is not
immutable, because a migration re-points it, but a change made by anything
outside the operator's namespace is rejected.
Required: {}
socketId string SocketID is the NUMA socket this node is bound to, as declared in the node
set's socket list, so 0 or 1. With NodeIndex it decomposes Slot into the
pair a person reads; nothing but a print column consumes either.
It is not marked immutable, because where a node sits is a fact about the
host it runs on and a relocation moves it: the target worker's free socket
is not necessarily the source's. The StorageNode validating webhook
rejects a change made by an identity outside the operator's namespace,
which is the same treatment WorkerNode takes and for the same reason.
Optional: {}
nodeIndex integer NodeIndex is the position among the nodes sharing this socket, in
0..nodesPerSocket-1. See SocketID, whose guard it shares.
Minimum: 0
Optional: {}
slot integer Slot is which storage-node slot on this worker the object occupies, counted
from zero. A worker runs one node per socket per nodesPerSocket, and the
slot is the position among them. It is the identity the operator keys on:
the topology label the CSI driver reads is
storage.simplyblock.io/storage-node-uuid.., and the slot
outlives the node filling it, because only the UUID behind it changes when a
node is replaced or relocated.
Minimum: 0
Optional: {}
config StorageNodeConfig Config is this node's complete configuration, copied from the
ClusterDeploymentConfig entry that produced it. It is a copy rather than a
projection, because that document is ephemeral: nothing reads it once the
node exists, deleting it changes nothing, and editing it reaches only nodes
created afterward.
Required: {}

StorageNodeStatus

StorageNodeStatus is the observed state of one storage node.

Appears in: - StorageNode

Example:

phase: StorageNodePhase
step: KubeSnapshot
uuid: string
nodeAddTaskID: string
status: string
health: boolean
hostname: string
uptime: string
resources:
  cpu: integer
  memory: string
  volumes: integer
  devices:
    online: integer
    total: integer
  capacity:
    totalBytes: integer
    usedBytes: integer
    sampledAt: Time
ports:
  management: string
  nvmeof: integer
  lvol: integer
  rpc: integer
failureDomain: string
activeOpsRef: string
latencyMetrics:
  nodeUUID: string
  baselineP50NS: integer
  baselineP99NS: integer
  baselineMeasuredAt: Time
message: string
observedGeneration: integer
Field Description Default Validation
phase StorageNodePhase Phase is the operator's own view of this node, and the field its
provisioning branches on.
Enum: [Pending Provisioning Online Removing Removed Offline Degraded Failed]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the provisioning machine. The value is one of the
steps that machine declares.
Optional: {}
uuid string UUID is the backend node UUID. Empty means the node has neither been
provisioned nor adopted, and non-empty means steady state.
Optional: {}
nodeAddTaskID string NodeAddTaskID is the control-plane task the last add of this node created. It is how
a node that is still being added reads why its own add is failing.
Optional: {}
status string Status is the lifecycle the control plane reports: online, suspended,
offline, in_creation, in_restart, in_shutdown, unreachable, or timeout. The
values are the control plane's, which is why they are neither PascalCase nor
constrained by an Enum here.
Optional: {}
health boolean Health is the health flag the control plane reports. Optional: {}
hostname string Hostname is the node hostname as the control plane reports it. Optional: {}
uptime string Uptime is the node uptime as the control plane reports it. Optional: {}
resources StorageNodeResources Resources groups the reported compute and storage figures. Optional: {}
ports StorageNodePorts Ports groups the reported addresses and ports. Optional: {}
failureDomain string FailureDomain is the failure-domain label the control plane actually
assigned, which is not necessarily the one spec.config.failureDomain
requested.
Optional: {}
activeOpsRef string ActiveOpsRef names the StorageNodeOps currently allowed to touch this node.
Empty when none is running.
Optional: {}
latencyMetrics NodeLatencyMetrics LatencyMetrics holds the fio-measured NVMe-oF baseline the volume
rebalancer reads.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as the
node moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

StorageNodesSpec

StorageNodesSpec is the Kubernetes workload every storage node in the cluster runs as: a DaemonSet, a headless Service and its EndpointSlices, a serving certificate, a ServiceAccount, and the ConfigMap the init container reads its per-node configuration out of.

Every field here is cluster-uniform by construction, because a DaemonSet is one object for every node it schedules and its pod template cannot differ per node. What can differ is in StorageNode.spec.config: the two images, the SPDK system memory, and the sizing block, which are per node precisely so that an image rollout and a hardware re-size can walk the fleet one machine at a time.

Appears in: - StorageClusterSpec

Example:

image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
mgmtInterface: string
dataInterfaces:
  - string
socketsToUse:
  - string
nodesPerSocket: integer
nodeProvisioningBudget: integer
enableJournalDevice: boolean
enableFormat4K: boolean
enableBlockFormat: boolean
enableCpuTopology: boolean
reservedSystemCPU: string
enableKubeletConfiguration: boolean
ubuntuHost: boolean
openshift:
  machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
tolerations:
  - Toleration
containerResources: ResourceRequirements
initContainerResources: ResourceRequirements
Field Description Default Validation
image string Image is the storage-node container image. Defaults to the ControlPlane
singleton's spec.image when unset, so a deployment states the version once.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Optional: {}
imagePullPolicy PullPolicy ⧉ ImagePullPolicy controls when that image is pulled. It defaults to Always
for the reason SimplyblockDriver's does: the images this product ships are
moving tags, so a node brought up after a release that kept IfNotPresent
would run whatever its kubelet already held. The workload builder has
always meant this and falls back to Always for an unset policy, a fallback
the schema default means it never reaches.
Always Enum: [Always Never IfNotPresent]
Optional: {}
mgmtInterface string MgmtInterface is the management network interface storage nodes bind. Optional: {}
dataInterfaces string array DataInterfaces are the data-plane network interfaces. Optional: {}
socketsToUse string array SocketsToUse restricts deployment to selected NUMA sockets. Empty means
socket 0 alone.
Optional: {}
nodesPerSocket integer NodesPerSocket is how many storage nodes run per NUMA socket. 1 Minimum: 1
Optional: {}
nodeProvisioningBudget integer NodeProvisioningBudget limits how many workers may be in the node-add
process at once, counted by distinct worker rather than by object so that a
two-socket host consumes one slot. Workers hosting a FoundationDB pod are
always sequential regardless of this value, because a node add reboots the
host and two simultaneous FoundationDB reboots reduce the control plane's
own fault tolerance.
1 Minimum: 1
Optional: {}
enableJournalDevice boolean EnableJournalDevice dedicates the smallest NVMe device on each node to the
journal manager, instead of carving a journal partition out of every
device.
Optional: {}
enableFormat4K boolean EnableFormat4K formats NVMe devices to a 4K block size where the device
supports it.
Optional: {}
enableBlockFormat boolean EnableBlockFormat wipes the partition tables and filesystem signatures
from this cluster's logical block devices, so that a disk carrying
something already becomes one a storage node can take.
It is the block class's half of the document's enableDriveFormat, and a
separate field because it is a separate operation on a separate channel:
EnableFormat4K is a reformat the control plane performs at node-add, and
this is a wipefs node_configure.py performs on the worker before the node
is added at all. A block device's block size is fixed by the drive, so
there is no reformat to ask for, and an NVMe controller is handed to SPDK
whole, so there are no signatures to wipe. Neither operation is available
in the other's class.
Destructive, and immutable for the reason the other is: it describes what
was done to the disks a fleet was built on.
Optional: {}
enableCpuTopology boolean EnableCpuTopology turns on topology-aware CPU assignment. Optional: {}
reservedSystemCPU string ReservedSystemCPU is the CPU set held back from SPDK for system workloads. Optional: {}
enableKubeletConfiguration boolean EnableKubeletConfiguration lets the storage node apply the kubelet
configuration changes it needs. Off by default, which is the behavior the
retired skipKubeletConfiguration expressed by being set.
Optional: {}
ubuntuHost boolean UbuntuHost states that the worker's host OS is Ubuntu, which changes how
the node configures huge pages and the kernel modules it loads.
Optional: {}
openshift OpenShiftSpec OpenShift is what this deployment states because it runs on OpenShift,
and its presence is that statement. See OpenShiftSpec.
Optional: {}
tolerations Toleration ⧉ array Tolerations are applied to the storage-node pods. Optional: {}
containerResources ResourceRequirements ⧉ ContainerResources sets requests and limits for the storage-node container.
Unset enforces no limits.
Optional: {}
initContainerResources ResourceRequirements ⧉ InitContainerResources does the same for the init container. Optional: {}

StoragePool

StoragePool carves a StorageCluster into a unit with its own capacity limit and QoS ceilings, and it is what a StorageClass is assigned to. It is therefore the join between the storage administrator's world and the application developer's, a join held together by three labels, an opaque parameter map, and a finalizer rather than by the API.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StoragePool
metadata:
  name: string
spec:
  clusterRef: string
  allowedNodes:
    - string
  limits:
    capacity: string
    maxVolumeSize: string
    iops: integer
    throughput:
      read: integer
      write: integer
      readWrite: integer
  volumeDefaults:
    iops: integer
    throughput:
      read: integer
      write: integer
      readWrite: integer
    filesystem: string
    enableCompression: boolean
    enableClientCompression: boolean
    enableClientDeduplication: boolean
    enableReplication: boolean
    enableDHCHAP: boolean
    priorityClass: string
    fabric: string
    maxNamespacesPerSubsystem: integer
    tune2fsReservedBlocks: string
status:
  phase: StoragePoolPhase
  uuid: string
  status: string
  storageClassNames:
    - string
  defaultStorageClassName: string
  limits:
    host: string
    iops: integer
    throughput:
      read: integer
      write: integer
      readWrite: integer
  allowedNodes:
    - string
  activeOpsRef: string
  message: string
  observedGeneration: integer
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StoragePool
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StoragePoolSpec
status StoragePoolStatus

StoragePoolOps

StoragePoolOps is a single operation performed against one StoragePool. Analogous to a Kubernetes Job: it drives an action to completion and records the result, and only one StoragePoolOps may be active per StoragePool at a time, which the pool's status.activeOpsRef enforces.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StoragePoolOps
metadata:
  name: string
spec:
  poolRef: string
  action: StoragePoolOpsAction
  abort: boolean
status:
  phase: StoragePoolOpsPhase
  step: KubeSnapshot
  message: string
  observedGeneration: integer
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StoragePoolOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StoragePoolOpsSpec
status StoragePoolOpsStatus

StoragePoolOpsAction

Underlying type: string

StoragePoolOpsAction is the operation a StoragePoolOps performs. The kind holds one action and that action is provisional: nothing implements it and nothing depends on it. It is declared because the motivation is real and cheaper to keep than to rediscover, not because it is work in progress.

Validation: - Enum: [Rebalance]

Appears in: - StoragePoolOpsSpec

Field Description
Rebalance StoragePoolOpsActionRebalance moves the pool's volumes off nodes
spec.allowedNodes no longer lists. It is a fan-out of PersistentVolumeOps
rather than a backend call: the control plane has no pool-granularity
rebalance and does not need one, because moving a volume is already an
operation this group has. The decision is the pool's and only the
execution is per-volume, which is why the action lives on this kind.

StoragePoolOpsPhase

Underlying type: string

StoragePoolOpsPhase is the operation's own progress.

Validation: - Enum: [Pending Running Succeeded Failed Aborted]

Appears in: - StoragePoolOpsStatus

Field Description
Pending StoragePoolOpsPhasePending is an operation waiting for its target's lock.
Running StoragePoolOpsPhaseRunning is an operation holding the lock and working.
Succeeded StoragePoolOpsPhaseSucceeded is a finished operation that did what it said.
Failed StoragePoolOpsPhaseFailed is a finished operation that did not.
Aborted StoragePoolOpsPhaseAborted is an operation stopped on request, whose
unwind has finished.

StoragePoolOpsSpec

StoragePoolOpsSpec is one operation to perform against one StoragePool.

Appears in: - StoragePoolOps

Example:

poolRef: string
action: StoragePoolOpsAction
abort: boolean
Field Description Default Validation
poolRef string PoolRef names the StoragePool this operation acts on, in this object's own
namespace. The operation never owns its target, because deleting the
record of an operation must not delete the pool it operated on.
Required: {}
action StoragePoolOpsAction Action is the operation to perform. Immutable: an operation that changed
what it was doing halfway through would have a status describing neither.
Enum: [Rebalance]
Required: {}
abort boolean Abort asks a running operation to stop at its next step and unwind. Optional: {}

StoragePoolOpsStatus

StoragePoolOpsStatus is the observed state of one pool operation.

Appears in: - StoragePoolOps

Example:

phase: StoragePoolOpsPhase
step: KubeSnapshot
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
Field Description Default Validation
phase StoragePoolOpsPhase Phase is the operation's own progress. Enum: [Pending Running Succeeded Failed Aborted]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running action's state machine. It is
persisted before the side effect that step performs, so a step reported
here is a step that started.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the operation moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
startedAt Time ⧉ StartedAt is when the operation acquired its target's lock. Optional: {}
completedAt Time ⧉ CompletedAt is when it reached a terminal phase. Optional: {}

StoragePoolPhase

Underlying type: string

StoragePoolPhase is where the operator has got to with this pool.

Validation: - Enum: [Pending Ready Deleting]

Appears in: - StoragePoolStatus

Field Description
Pending StoragePoolPhasePending is a pool that has no backend UUID yet, whether
because its cluster is not finished or because the create has not landed.
Ready StoragePoolPhaseReady is a pool the control plane has created and that
volumes can be provisioned from.
Deleting StoragePoolPhaseDeleting is a pool being torn down, which may be held for
a long time behind an assigned class or a bound volume.

StoragePoolSpec

StoragePoolSpec is the desired state of one tenancy unit within a cluster.

Appears in: - StoragePool

Example:

clusterRef: string
allowedNodes:
  - string
limits:
  capacity: string
  maxVolumeSize: string
  iops: integer
  throughput:
    read: integer
    write: integer
    readWrite: integer
volumeDefaults:
  iops: integer
  throughput:
    read: integer
    write: integer
    readWrite: integer
  filesystem: string
  enableCompression: boolean
  enableClientCompression: boolean
  enableClientDeduplication: boolean
  enableReplication: boolean
  enableDHCHAP: boolean
  priorityClass: string
  fabric: string
  maxNamespacesPerSubsystem: integer
  tune2fsReservedBlocks: string
Field Description Default Validation
clusterRef string ClusterRef names the StorageCluster this pool is carved out of, in this
pool's own namespace. The cluster owns this object by controller
reference, so deleting the cluster deletes its pools, held behind each
pool's own finalizer while classes are assigned or volumes are bound.
Immutable from creation: which cluster a pool is in is its identity.
The maximum is what a StorageCluster name may be rather than what a
reference may be: a longer value names nothing that can exist, and the
reference is immutable, so admitting one creates a pool whose only remedy
is deletion.
MaxLength: 63
Required: {}
allowedNodes string array AllowedNodes restricts which hosts may carry this pool's volumes, by
Kubernetes Node name. Empty means every node in the cluster. Narrowing it
stops new volumes landing on the removed nodes and leaves the existing
ones where they are. Requires volumeDefaults.enableDHCHAP to be true.
The list is left exactly as authored: a name that no longer resolves is
dropped from Status.AllowedNodes rather than pruned from here, so a node
removed for maintenance and added back under the same name returns to the
pools that named it without anybody re-authoring them.
Optional: {}
limits PoolLimits Limits are the ceilings the pool as a whole is held to. Mutable: raising a
pool's capacity is an ordinary operation the control plane supports, and it
does not touch any StorageClass.
Optional: {}
volumeDefaults VolumeDefaults VolumeDefaults are what every volume in the pool is created with.
Immutable once set, because StorageClass.parameters is immutable in the
Kubernetes API: a pool whose defaults changed would have a class the
operator cannot update. Changing them means creating a new pool.
Optional: {}

StoragePoolStatus

StoragePoolStatus is the observed state of one pool.

Appears in: - StoragePool

Example:

phase: StoragePoolPhase
uuid: string
status: string
storageClassNames:
  - string
defaultStorageClassName: string
limits:
  host: string
  iops: integer
  throughput:
    read: integer
    write: integer
    readWrite: integer
allowedNodes:
  - string
activeOpsRef: string
message: string
observedGeneration: integer
Field Description Default Validation
phase StoragePoolPhase Phase is the operator's own view of this pool. Enum: [Pending Ready Deleting]
Optional: {}
uuid string UUID is the backend pool UUID. Empty means the pool has not been created,
and it is the field the controller branches on.
Optional: {}
status string Status is the lifecycle the control plane reports, in the control plane's
own spelling, which is why it carries no Enum here.
Optional: {}
storageClassNames string array StorageClassNames are the classes assigned to this pool, found by the
three storage.simplyblock.io labels a class carries. Empty means no class
draws from this pool yet, which is a valid state and what a freshly
created cluster's default pool has before anybody writes one. Publishing
it is what makes the assignment readable from the pool, and what a
deletion is held on.
Optional: {}
defaultStorageClassName string DefaultStorageClassName is the class the operator wrote for the default
pool, set on that pool only. It records that the class was created, so a
class missing while this is set was deleted deliberately and is not
written again.
Optional: {}
limits PoolLimitsStatus Limits is what the control plane reports the ceilings to be. Optional: {}
allowedNodes string array AllowedNodes is Spec.AllowedNodes resolved against the Node objects that
exist, which is what the control plane's host list and the per-pool node
labels are derived from. An empty list here is not the same as an absent
Spec.AllowedNodes: absent means every node, and empty after resolution
means the pool can place nothing.
Optional: {}
activeOpsRef string ActiveOpsRef names the StoragePoolOps currently allowed to act on this
pool. Empty when none is running.
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the pool moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}

StorageSiteCluster

StorageSiteCluster is the StorageCluster the approved draft produced.

Appears in: - StorageSiteDeploymentStatus

Example:

name: string
uuid: string
phase: string
pool: string
nodes:
  - name: string
    phase: string
    hostname: string
Field Description Default Validation
name string Name is the StorageCluster object on the site.
uuid string UUID is the storage cluster's id in the control plane, which a
StorageClass names in cluster_id.
Optional: {}
phase string Phase is the StorageCluster's phase on the site. Optional: {}
pool string Pool is the pool the cluster was created with, which a StorageClass names
in pool_name.
Optional: {}
nodes StorageSiteNode array Nodes are the cluster's storage nodes. Optional: {}

StorageSiteDeployment

StorageSiteDeployment requests a managed site's storage cluster from the hub: a discovery on the site, the sizing of the draft it writes, and the approval that expands the draft into a StorageCluster. The hub carries the request through OCM and projects the site's draft and cluster into the status. Deleting the request leaves the storage cluster alone.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageSiteDeployment
metadata:
  name: string
spec:
  cluster: string
  siteNamespace: string
  draftName: string
  discover:
    enableControlPlaneNodes: boolean
    workers:
      - string
    nodeSelector:
      string: string
  sizing:
    name: string
    vcpuCount: integer
    minHugePagesSize: string
    maxSubsystemCount: integer
    enableDriveFormat: boolean
    enableJournalDevice: boolean
    stripe:
      dataChunks: integer
      parityChunks: integer
  approved: boolean
status:
  phase: StorageSiteDeploymentPhase
  message: string
  observedGeneration: integer
  workName: string
  draft:
    name: string
    phase: string
    message: string
    approved: boolean
    cluster:
      name: string
      maxSubsystemCount: integer
      vcpuCount: integer
      minHugePagesSize: string
      enableDriveFormat: boolean
      enableJournalDevice: boolean
      socketsToUse:
        - string
      nodesPerSocket: integer
      initContainerResources: ResourceRequirements
      tolerations:
        - Toleration
      containerResources: ResourceRequirements
      nodeProvisioningBudget: integer
      enableChecksumValidation: boolean
      enableAtomicity4K: boolean
      stripe:
        dataChunks: integer
        parityChunks: integer
      fabricType: string
      openshift:
        machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
      ports:
        nvmf: integer
        rpc: integer
        nodeAgent: integer
      enableFailureDomains: boolean
      enableNodeAffinity: boolean
      backup:
        endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
        bucket: string
        region: string
        credentialsSecretRef: LocalObjectReference
      kms:
        vault:
          endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
    nodeSets:
      - name: string
        groups:
          - name: string
            workers:
              - string
            mgmtInterface: string
            dataInterfaces:
              - string
            devices:
              nvme:
                - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
              block:
                - '^/dev/[a-zA-Z0-9._/-]+$'
            failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
            spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
            reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
            journalManager:
              count: integer
              percentPerDevice: integer
    nodeRefs:
      - string
  storageCluster:
    name: string
    uuid: string
    phase: string
    pool: string
    nodes:
      - name: string
        phase: string
        hostname: string
  conditions:
    - Condition
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string StorageSiteDeployment
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec StorageSiteDeploymentSpec
status StorageSiteDeploymentStatus

StorageSiteDeploymentPhase

Underlying type: string

StorageSiteDeploymentPhase is the request's own progress.

Validation: - Enum: [Pending Discovering Drafted Deploying Online Failed]

Appears in: - StorageSiteDeploymentStatus

Field Description
Pending StorageSiteDeploymentPhasePending is the request before the hub delivered
anything to the site.
Discovering StorageSiteDeploymentPhaseDiscovering is the discovery running on the site:
the draft is not written yet, or names no node yet.
Drafted StorageSiteDeploymentPhaseDrafted is a draft with nodes on the site, sized
as the request says, awaiting approval.
Deploying StorageSiteDeploymentPhaseDeploying is an approved draft expanding into a
StorageCluster that is not Online yet.
Online StorageSiteDeploymentPhaseOnline is the StorageCluster Online on the site.
Failed StorageSiteDeploymentPhaseFailed is the site's own failure: the draft or the
StorageCluster failed, or the hub could not deliver the request.

StorageSiteDeploymentSpec

StorageSiteDeploymentSpec is the request for one site's storage cluster.

Appears in: - StorageSiteDeployment

Example:

cluster: string
siteNamespace: string
draftName: string
discover:
  enableControlPlaneNodes: boolean
  workers:
    - string
  nodeSelector:
    string: string
sizing:
  name: string
  vcpuCount: integer
  minHugePagesSize: string
  maxSubsystemCount: integer
  enableDriveFormat: boolean
  enableJournalDevice: boolean
  stripe:
    dataChunks: integer
    parityChunks: integer
approved: boolean
Field Description Default Validation
cluster string Cluster is the OCM ManagedCluster the storage is deployed on. The request's
ManifestWork and views live in its namespace on the hub. Immutable.
MaxLength: 63
MinLength: 1
Required: {}
siteNamespace string SiteNamespace is the simplyblock operator's namespace on the site, where
the discovery and the draft live.
simplyblock MaxLength: 63
Optional: {}
draftName string DraftName is the ClusterDeploymentConfig the discovery writes on the site
and the request sizes and approves. Immutable.
site-draft MaxLength: 63
Optional: {}
discover StorageSiteDiscovery Discover is the discovery the site runs first. Changing it runs another
discovery, which rewrites the draft.
Optional: {}
sizing StorageSiteSizing Sizing is written onto the draft's cluster template once the draft exists,
so the reviewer sees the sized draft before approving it.
Optional: {}
approved boolean Approved is the review gate, delivered to the draft on the site. One-way,
as the draft's own gate is.
false Optional: {}

StorageSiteDeploymentStatus

StorageSiteDeploymentStatus is what the site reports back, projected.

Appears in: - StorageSiteDeployment

Example:

phase: StorageSiteDeploymentPhase
message: string
observedGeneration: integer
workName: string
draft:
  name: string
  phase: string
  message: string
  approved: boolean
  cluster:
    name: string
    maxSubsystemCount: integer
    vcpuCount: integer
    minHugePagesSize: string
    enableDriveFormat: boolean
    enableJournalDevice: boolean
    socketsToUse:
      - string
    nodesPerSocket: integer
    initContainerResources: ResourceRequirements
    tolerations:
      - Toleration
    containerResources: ResourceRequirements
    nodeProvisioningBudget: integer
    enableChecksumValidation: boolean
    enableAtomicity4K: boolean
    stripe:
      dataChunks: integer
      parityChunks: integer
    fabricType: string
    openshift:
      machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
    ports:
      nvmf: integer
      rpc: integer
      nodeAgent: integer
    enableFailureDomains: boolean
    enableNodeAffinity: boolean
    backup:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
      bucket: string
      region: string
      credentialsSecretRef: LocalObjectReference
    kms:
      vault:
        endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
  nodeSets:
    - name: string
      groups:
        - name: string
          workers:
            - string
          mgmtInterface: string
          dataInterfaces:
            - string
          devices:
            nvme:
              - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
            block:
              - '^/dev/[a-zA-Z0-9._/-]+$'
          failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
          spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
          reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
          journalManager:
            count: integer
            percentPerDevice: integer
  nodeRefs:
    - string
storageCluster:
  name: string
  uuid: string
  phase: string
  pool: string
  nodes:
    - name: string
      phase: string
      hostname: string
conditions:
  - Condition
Field Description Default Validation
phase StorageSiteDeploymentPhase Phase is the request's own progress. Enum: [Pending Discovering Drafted Deploying Online Failed]
Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the request moves, and never a log.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from.
Optional: {}
workName string WorkName is the ManifestWork carrying the request to the site. Optional: {}
draft StorageSiteDraft Draft is the draft as the site reports it. Optional: {}
storageCluster StorageSiteCluster StorageCluster is the cluster the approved draft produced. Optional: {}
conditions Condition ⧉ array Conditions: Delivered (the work is applied on the site), Discovered (the
draft names nodes), Approved (the site's draft is approved), Ready (the
StorageCluster is Online).
Optional: {}

StorageSiteDiscovery

StorageSiteDiscovery is the discovery the site runs: which nodes are inspected. It is the hub-side form of OperatorOps.spec.discover.

Appears in: - StorageSiteDeploymentSpec

Example:

enableControlPlaneNodes: boolean
workers:
  - string
nodeSelector:
  string: string
Field Description Default Validation
enableControlPlaneNodes boolean EnableControlPlaneNodes lets the discovery consider the nodes that run the
API server. Every server of a small distribution is one, so a three-node
site has no storage without it.
Optional: {}
workers string array Workers limits the discovery to these nodes. Empty is every worker. Optional: {}
nodeSelector object (keys:string, values:string) NodeSelector limits the discovery to the nodes carrying these labels. Optional: {}

StorageSiteDraft

StorageSiteDraft is the draft as the site reports it.

Appears in: - StorageSiteDeploymentStatus

Example:

name: string
phase: string
message: string
approved: boolean
cluster:
  name: string
  maxSubsystemCount: integer
  vcpuCount: integer
  minHugePagesSize: string
  enableDriveFormat: boolean
  enableJournalDevice: boolean
  socketsToUse:
    - string
  nodesPerSocket: integer
  initContainerResources: ResourceRequirements
  tolerations:
    - Toleration
  containerResources: ResourceRequirements
  nodeProvisioningBudget: integer
  enableChecksumValidation: boolean
  enableAtomicity4K: boolean
  stripe:
    dataChunks: integer
    parityChunks: integer
  fabricType: string
  openshift:
    machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
  ports:
    nvmf: integer
    rpc: integer
    nodeAgent: integer
  enableFailureDomains: boolean
  enableNodeAffinity: boolean
  backup:
    endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
    bucket: string
    region: string
    credentialsSecretRef: LocalObjectReference
  kms:
    vault:
      endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
nodeSets:
  - name: string
    groups:
      - name: string
        workers:
          - string
        mgmtInterface: string
        dataInterfaces:
          - string
        devices:
          nvme:
            - '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
          block:
            - '^/dev/[a-zA-Z0-9._/-]+$'
        failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
        spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
        reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
        journalManager:
          count: integer
          percentPerDevice: integer
nodeRefs:
  - string
Field Description Default Validation
name string Name is the ClusterDeploymentConfig on the site.
phase string Phase is the draft's own phase on the site (Draft, Expanding, Expanded,
Failed).
Optional: {}
message string Message is what the site says about the draft: validation findings while
it is a draft, the expansion's step afterwards.
Optional: {}
approved boolean Approved is whether the draft is approved on the site. Optional: {}
cluster ClusterTemplate Cluster is the draft's cluster template, with the sizing applied. Optional: {}
nodeSets NodeSet array NodeSets are the nodes and devices the discovery found, for review. Optional: {}
nodeRefs string array NodeRefs are the StorageNode objects the expansion created. Optional: {}

StorageSiteNode

StorageSiteNode is one storage node of the deployed cluster, as the site reports it.

Appears in: - StorageSiteCluster

Example:

name: string
phase: string
hostname: string
Field Description Default Validation
name string Name is the StorageNode object on the site.
phase string Phase is the node's phase on the site. Optional: {}
hostname string Hostname is the Kubernetes node it runs on. Optional: {}

StorageSiteSizing

StorageSiteSizing is the cluster template written onto the draft before it is approved: the fields of ClusterDeploymentConfig.spec.cluster a reviewer decides. Absent fields keep what the discovery wrote.

Appears in: - StorageSiteDeploymentSpec

Example:

name: string
vcpuCount: integer
minHugePagesSize: string
maxSubsystemCount: integer
enableDriveFormat: boolean
enableJournalDevice: boolean
stripe:
  dataChunks: integer
  parityChunks: integer
Field Description Default Validation
name string Name is the StorageCluster's name on the site. MaxLength: 63
Optional: {}
vcpuCount integer VCPUCount is the number of vCPUs each storage node takes. Minimum: 1
Optional: {}
minHugePagesSize string MinHugePagesSize is the hugepage memory each storage node takes, as a
quantity ("8G").
Optional: {}
maxSubsystemCount integer MaxSubsystemCount is the number of NVMe-oF subsystems each node serves. Minimum: 1
Optional: {}
enableDriveFormat boolean EnableDriveFormat lets the deployment format the devices it takes. Optional: {}
enableJournalDevice boolean EnableJournalDevice dedicates one device per node to the journal. Optional: {}
stripe StripeSpec Stripe is the erasure-coding layout. Optional: {}

StripeSpec

StripeSpec is the erasure-coding layout: how many data chunks a stripe carries and how many parity chunks protect them.

The pair is one of the seven schemes simplyblock supports, and the rule below is the same set the control plane holds in SUPPORTED_ERASURE_CODING_SCHEMES. It is stated here as well because the control plane's refusal arrives at the cluster create, which is several steps and — for a cluster a deployment config produced — one irreversible approval after the apply that stated the scheme.

Each scheme also has a storage-node count below which it must not be used, which is ndcs+npcs nodes to place a stripe across plus one spare per tolerated failure to rebuild onto: 1 for 1+0, 3 for 1+1, 4 for 2+1, 6 for 4+1, 5 for 1+2, 6 for 2+2, and 8 for 4+2. That is not expressible here, because the nodes are objects of their own and a cluster is created before any of them exists. It is answered by the deployment config's validation, by its approval webhook, and by the cluster's activation gate.

Appears in: - ClusterTemplate - StorageClusterSpec - StorageSiteSizing

Example:

dataChunks: integer
parityChunks: integer
Field Description Default Validation
dataChunks integer DataChunks is the number of data chunks per stripe (ndcs). Minimum: 1
Optional: {}
parityChunks integer ParityChunks is the number of parity chunks per stripe (npcs), and
therefore how many chunk losses a stripe survives.
Minimum: 0
Optional: {}

TestFailover

TestFailover is a one-way, non-disruptive test-failover drill. It recovers a source volume, or a consistency group, from a snapshot into an isolated namespace on a chosen cluster as bound PVCs, without touching the source. The hub reads the source on its cluster and places the bubble on the recovery cluster through OCM. It runs to a terminal phase, or holds Ready until it is deleted, and deletion reclaims the clones and any snapshots the drill took.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: TestFailover
metadata:
  name: string
spec:
  scope: TestFailoverScope
  sourceCluster: string
  sourceNamespace: string
  sourceRef: string
  bubbleCluster: string
  bubbleNamespace: string
  ttlSeconds: integer
status:
  phase: TestFailoverPhase
  step: KubeSnapshot
  message: string
  triggered: boolean
  observedGeneration: integer
  clones:
    - sourceRef: string
      sourceHandle: string
      snapshotID: string
      cloneID: string
      pvcName: string
      sizeBytes: integer
      sourceVolumeContext:
        string: string
      sourceFSType: string
      sourceVolumeMode: string
  report:
    bubbleCluster: string
    recoveryPoint: string
    recoveryPointTime: Time
    recoveryPointAgeSeconds: integer
    invariantsHeld: boolean
  startedAt: Time
  readyAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string TestFailover
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec TestFailoverSpec
status TestFailoverStatus

TestFailoverClone

TestFailoverClone is one recovered volume: the source it came from, the snapshot and clone the drill built, and the PVC placed on the bubble cluster.

Appears in: - TestFailoverStatus

Example:

sourceRef: string
sourceHandle: string
snapshotID: string
cloneID: string
pvcName: string
sizeBytes: integer
sourceVolumeContext:
  string: string
sourceFSType: string
sourceVolumeMode: string
Field Description Default Validation
sourceRef string SourceRef is the source volume, or group member, the recovered volume maps
to.
sourceHandle string SourceHandle is the source volume's backend handle, read from its PV. Optional: {}
snapshotID string SnapshotID is the recovery-point snapshot: the replicated snapshot already on
the bubble cluster's backend that the clone is built from.
Optional: {}
cloneID string CloneID is the backend id of the writable clone. Optional: {}
pvcName string PVCName is the bound PVC in the bubble namespace on the bubble cluster. Optional: {}
sizeBytes integer SizeBytes is the recovered volume's size. Optional: {}
sourceVolumeContext object (keys:string, values:string) SourceVolumeContext is the source PV's CSI volumeAttributes, minus the
identity and provisioner keys, carried onto the bubble PV so the node plugin
receives a non-nil VolumeContext when it stages the clone. The clone's own
identity (NQN, connections, nsId, and so on) is re-resolved from the clone
handle at stage time, so only the class-level parameters are carried; the
identity keys are dropped so a failed clone lookup can never point the mount
back at the source.
Optional: {}
sourceFSType string SourceFSType is the source PV's CSI fsType, carried onto the bubble PV so the
node plugin stages the clone with the filesystem it actually carries. The
clone is a block copy of the source, so its filesystem is the source's; an
empty fsType makes the node plugin default to ext4 and refuse to mount an XFS
volume.
Optional: {}
sourceVolumeMode string SourceVolumeMode is the source PV's volumeMode (Filesystem or Block),
carried onto the bubble PV and PVC. A VM's disk is a Block claim; a bubble
claim that omitted the mode defaulted to Filesystem and the kubelet asked
the node plugin to mount a raw guest disk (2026-10-03).
Optional: {}

TestFailoverPhase

Underlying type: string

TestFailoverPhase is the drill's own progress.

Validation: - Enum: [Pending Provisioning Ready Failed TearingDown]

Appears in: - TestFailoverStatus

Field Description
Pending
Provisioning
Ready
Failed
TearingDown

TestFailoverReport

TestFailoverReport is the evidence a drill produces.

Appears in: - TestFailoverStatus

Example:

bubbleCluster: string
recoveryPoint: string
recoveryPointTime: Time
recoveryPointAgeSeconds: integer
invariantsHeld: boolean
Field Description Default Validation
bubbleCluster string BubbleCluster is the cluster the drill recovered onto. Optional: {}
recoveryPoint string RecoveryPoint is the snapshot or group generation the drill recovered. Optional: {}
recoveryPointTime Time ⧉ RecoveryPointTime is when that point was taken. Optional: {}
recoveryPointAgeSeconds integer RecoveryPointAgeSeconds is the drill time minus the recovery-point time. Optional: {}
invariantsHeld boolean InvariantsHeld is true only when the source fingerprint taken before the
drill matches the one taken at Ready. A Ready drill with this false is a
defect.
Optional: {}

TestFailoverScope

Underlying type: string

TestFailoverScope selects what a drill recovers.

Validation: - Enum: [Volume Group]

Appears in: - TestFailoverSpec

Field Description
Volume TestFailoverScopeVolume recovers a single source volume, named by a PVC.
Group TestFailoverScopeGroup recovers a consistency group from one
group-consistent point.

TestFailoverSpec

TestFailoverSpec is the request for one non-disruptive test-failover drill.

The source is named by where it runs and what it is, so the hub can find it without anyone extracting a backend handle by hand. SourceNamespace is required for a Volume drill, where the source is a PVC, and unused for a Group drill, where SourceRef names a consistency group.

Appears in: - TestFailover

Example:

scope: TestFailoverScope
sourceCluster: string
sourceNamespace: string
sourceRef: string
bubbleCluster: string
bubbleNamespace: string
ttlSeconds: integer
Field Description Default Validation
scope TestFailoverScope Scope selects what the drill recovers. Immutable. Enum: [Volume Group]
Required: {}
sourceCluster string SourceCluster is the OCM ManagedCluster the source runs on. The hub reads
the source there through a ManagedClusterView. Immutable.
Required: {}
sourceNamespace string SourceNamespace is the namespace of the source PVC on SourceCluster.
Required for scope=Volume. Immutable.
Optional: {}
sourceRef string SourceRef names the source on SourceCluster: a PersistentVolumeClaim in
SourceNamespace (scope=Volume), or a consistency group (scope=Group).
Immutable.
Required: {}
bubbleCluster string BubbleCluster is the OCM ManagedCluster to recover onto: a DR target holding
the replicated point, or another cluster. It must differ from SourceCluster;
test-failover recovers onto a different cluster, never in place. Immutable.
Required: {}
bubbleNamespace string BubbleNamespace is the namespace on the bubble cluster where the recovered
PVCs are created. Immutable.
bubble Optional: {}
ttlSeconds integer TTLSeconds is an optional maximum lifetime: the drill is torn down after it
even without a delete, so a forgotten drill cannot hold a clone forever.
Minimum: 0
Optional: {}

TestFailoverStatus

TestFailoverStatus is the observed state of one drill.

Appears in: - TestFailover

Example:

phase: TestFailoverPhase
step: KubeSnapshot
message: string
triggered: boolean
observedGeneration: integer
clones:
  - sourceRef: string
    sourceHandle: string
    snapshotID: string
    cloneID: string
    pvcName: string
    sizeBytes: integer
    sourceVolumeContext:
      string: string
    sourceFSType: string
    sourceVolumeMode: string
report:
  bubbleCluster: string
  recoveryPoint: string
  recoveryPointTime: Time
  recoveryPointAgeSeconds: integer
  invariantsHeld: boolean
startedAt: Time
readyAt: Time
completedAt: Time
Field Description Default Validation
phase TestFailoverPhase Phase is the drill's own progress. Enum: [Pending Provisioning Ready Failed TearingDown]
Optional: {}
step KubeSnapshot ⧉ Step is the position of the running drill's state machine. Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as the
drill moves, and never a log.
Optional: {}
triggered boolean Triggered records that the current step's side effect was issued, so a
restart does not repeat it.
Optional: {}
observedGeneration integer ObservedGeneration is the generation the rest of this status was computed
from, so a stale status can be told from a current one.
Optional: {}
clones TestFailoverClone array Clones is one entry per recovered volume. Optional: {}
report TestFailoverReport Report is the drill's evidence, populated as it reaches Ready. Optional: {}
startedAt Time ⧉ StartedAt is when the drill started. Optional: {}
readyAt Time ⧉ ReadyAt is when every recovered PVC became bound. Optional: {}
completedAt Time ⧉ CompletedAt is when the drill reached a terminal phase. Optional: {}

ThroughputLimits

ThroughputLimits are throughput ceilings in megabytes per second. The unit is the field's rather than the value's, which is why the class keys these reach spell it out: a parameter map has no type to carry it.

Appears in: - PoolLimits - PoolLimitsStatus - VolumeDefaults

Example:

read: integer
write: integer
readWrite: integer
Field Description Default Validation
read integer Read is the read-only ceiling, written as max_read_mbytes_per_sec. Minimum: 0
Optional: {}
write integer Write is the write-only ceiling, written as max_write_mbytes_per_sec. Minimum: 0
Optional: {}
readWrite integer ReadWrite is the ceiling on both directions together, written as
max_mbytes_per_sec. It is not an access mode, which is the confusion the
older class key qos_rw_mbytes invited.
Minimum: 0
Optional: {}

UpgradeSpec

UpgradeSpec parameterizes the Upgrade action and is ignored by the others.

Appears in: - ControlPlaneOpsSpec

Example:

image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
Field Description Default Validation
image string Image is the version to move to. It replaces
ControlPlane.spec.source.managed.image when the operation succeeds, so the
entity keeps describing what is running.
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$
Required: {}

ValidationJob

ValidationJob is one Job started to check a path is reachable from one node.

It is tracked so that Verifying can delete every Job the operation started, including after a restart. The namespace is recorded for the same reason spec.migrate.targetNodeRef carries one: a Job is namespaced and this object is not, so a name alone would not locate it.

Appears in: - MigrationStatus

Example:

namespace: string
name: string
node: string
succeeded: boolean
Field Description Default Validation
namespace string Required: {}
name string Required: {}
node string Node is the worker the Job is pinned to, which is a node consuming one of
the migrated subsystem's volumes.
Optional: {}
succeeded boolean Succeeded records a node whose paths were checked and found ready, so a
restart does not run the check again on a node that already passed and
whose Job its own TTL may already have reaped.
Optional: {}

VaultKMS

VaultKMS configures the HashiCorp Vault key store.

Appears in: - KMSSpec

Example:

endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
Field Description Default Validation
endpoint string Endpoint is the Vault endpoint, for example, https://vault.example.com:8200.
Rejected unless it resolves to an external address.
Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$
Required: {}

VolumeAutoPlacementSettings

VolumeAutoPlacementSettings configures automatic, latency-driven volume rebalancing. Whether it runs at all is StorageClusterSpec.EnableVolumeAutoPlacement.

Appears in: - StorageClusterSpec

Example:

disableMigration: boolean
evaluationInterval: Duration
imbalanceThreshold: integer
minHotColdDifferencePct: integer
defaultCoolDownSeconds: integer
maxVolumeMigrationsPerCycle: integer
storageNodeCandidateCount: integer
metricsBackend: MetricsBackend
prometheusURL: string
enableLatencyBenchmark: boolean
latencyBenchmarkInterval: Duration
baselineStrategy: BaselineStrategy
baselineWindow: Duration
baselineColdStart: BaselineColdStartPolicy
baselineMinSamples: integer
baselineOutlierK: float
iopsWeight: float
throughputWeight: float
Field Description Default Validation
disableMigration boolean DisableMigration stops the rebalancer creating PersistentVolume
migrations while leaving every other part of a cycle running: load is
evaluated, deviations are computed, candidates are selected, and metrics
are emitted, and the migrations are discarded rather than created. It is
the dry run, and it is spelled as a disable because the behavior it
governs is on by default.
Optional: {}
evaluationInterval Duration ⧉ EvaluationInterval is how often the rebalancer evaluates load. Defaults
to 60s.
Optional: {}
imbalanceThreshold integer ImbalanceThreshold is the latency deviation from baseline, in percent, a
node must exceed before it is considered a rebalancing source. Defaults
to 80.
Optional: {}
minHotColdDifferencePct integer MinHotColdDifferencePct is how far below the hot source, in percentage
points of deviation, a candidate target must be before a volume is moved.
It is what stops a migration between two near-equally loaded nodes.
Defaults to 20.
Optional: {}
defaultCoolDownSeconds integer DefaultCoolDownSeconds is how long a volume is left alone after it has
been migrated. Defaults to 600.
Optional: {}
maxVolumeMigrationsPerCycle integer MaxVolumeMigrationsPerCycle caps how many volumes one cycle moves.
Defaults to 10.
Optional: {}
storageNodeCandidateCount integer StorageNodeCandidateCount is how many of the most loaded nodes are
evaluated each cycle to find the best source. Defaults to 3.
Optional: {}
metricsBackend MetricsBackend MetricsBackend selects the source of I/O metrics. Defaults to Prometheus. Enum: [ControlPlane Prometheus Uniform]
Optional: {}
prometheusURL string PrometheusURL is required when MetricsBackend is Prometheus. Optional: {}
enableLatencyBenchmark boolean EnableLatencyBenchmark turns on fio-based NVMe-oF latency measurement
through Kubernetes Jobs. It is off unless a RebalancerImage is
configured.
Optional: {}
latencyBenchmarkInterval Duration ⧉ LatencyBenchmarkInterval is how often those Jobs run against each storage
node. It also sets the step of the rolling-window baseline query, which
is the cadence at which the probe sidecar publishes samples. Defaults to
5m.
Optional: {}
baselineStrategy BaselineStrategy BaselineStrategy selects how the per-node baseline is derived. Defaults
to RollingWindow.
Enum: [Benchmark RollingWindow]
Optional: {}
baselineWindow Duration ⧉ BaselineWindow is the look-back the RollingWindow strategy reduces.
Defaults to 6h.
Optional: {}
baselineColdStart BaselineColdStartPolicy BaselineColdStart selects what happens to an under-sampled node. Defaults
to PartialWindow.
Enum: [Defer PartialWindow]
Optional: {}
baselineMinSamples integer BaselineMinSamples is the sample count below which a node counts as
under-sampled. Defaults to 6.
Optional: {}
baselineOutlierK float BaselineOutlierK is the Hampel-identifier threshold: a sample is rejected
when it lies more than k·1.4826·MAD from the window median, so a lower
value rejects more. Defaults to 3.0.
Optional: {}
iopsWeight float IOPSWeight weights per-volume IOPS in the volume I/O score. Defaults to
1.0.
Optional: {}
throughputWeight float ThroughputWeight weights per-volume throughput, in MB/s, in the volume
I/O score. Defaults to 0.1.
Optional: {}

VolumeDefaults

VolumeDefaults are the defaults every volume in the pool is created with. They are what a StorageClass assigned to this pool is expected to carry in its parameters, which is how they reach the CSI driver, and the whole block is immutable once set for that reason: StorageClass.parameters is immutable in the Kubernetes API, so a pool whose defaults changed would have a class the operator cannot update and a spec that no longer describes it.

Appears in: - StoragePoolSpec

Example:

iops: integer
throughput:
  read: integer
  write: integer
  readWrite: integer
filesystem: string
enableCompression: boolean
enableClientCompression: boolean
enableClientDeduplication: boolean
enableReplication: boolean
enableDHCHAP: boolean
priorityClass: string
fabric: string
maxNamespacesPerSubsystem: integer
tune2fsReservedBlocks: string
Field Description Default Validation
iops integer IOPS is each volume's ceiling on operations per second, both directions
together. Zero is unlimited. It is written into the class as max_iops.
Minimum: 0
Optional: {}
throughput ThroughputLimits Throughput is each volume's throughput ceiling, in megabytes per second. Optional: {}
filesystem string Filesystem is what a volume is formatted with. The values are the kernel's
names for filesystems, which is why they are not recased: this group did
not invent the words.
xfs Enum: [ext4 xfs]
Optional: {}
enableCompression boolean EnableCompression compresses logical volumes. Optional: {}
enableClientCompression boolean EnableClientCompression compresses each volume on the node that consumes
it (VDO), before a write ever reaches the wire, rather than on the storage
node. Distinct from EnableCompression, and independent of
EnableClientDeduplication: either, both, or neither may be set. A volume
requesting this is pinned to a node whose kernel can run dm-vdo.
Optional: {}
enableClientDeduplication boolean EnableClientDeduplication deduplicates each volume on the node that
consumes it (VDO), independent of EnableClientCompression. It carries a
significant fixed RAM cost per volume for VDO's index, so it is meant for
the pools where duplicate data is actually expected (VM images, container
layers, backup targets) rather than enabled by default.
Optional: {}
enableReplication boolean EnableReplication replicates logical volumes. Optional: {}
enableDHCHAP boolean EnableDHCHAP authenticates NVMe-oF connections to this pool's volumes.
Authentication is only enforced when AllowedNodes is non-empty, because
the generated class gets its node selector from that list and a class's
parameters cannot be edited afterward.
Optional: {}
priorityClass string PriorityClass is the logical-volume priority class the control plane
places with.
Optional: {}
fabric string Fabric is the storage fabric a volume is served over, defaulting to the
cluster's.
Optional: {}
maxNamespacesPerSubsystem integer MaxNamespacesPerSubsystem caps how many namespaces share one NVMe-oF
subsystem.
Minimum: 1
Optional: {}
tune2fsReservedBlocks string Tune2fsReservedBlocks is the reserved-block percentage passed to tune2fs
on an ext4 volume. Empty means the filesystem's own default, which is not
the same as 0: the node plugin skips the call entirely when this is
empty, and runs tune2fs -m 0 when it is 0.
Optional: {}

VolumeGroupSnapshotOps

VolumeGroupSnapshotOps is a one-shot operation on a VolumeGroupSnapshot, analogous to a Kubernetes Job: it drives its action to completion, records the result, and is then inert. The Restore action creates one PersistentVolumeClaim per member snapshot of the target's generation and waits for every claim to bind.

Example:

apiVersion: storage.simplyblock.io/v1alpha2
kind: VolumeGroupSnapshotOps
metadata:
  name: string
spec:
  volumeGroupSnapshotRef: string
  action: VolumeGroupSnapshotOpsAction
  restore:
    namePrefix: string
    storageClassName: string
    consistencyGroup: string
    enablePartialRestore: boolean
status:
  phase: VolumeGroupSnapshotOpsPhase
  step:
    state: VolumeGroupSnapshotOpsStep
    deadline: Time
  message: string
  observedGeneration: integer
  membersExpected: integer
  membersBound: integer
  members:
    - volumeSnapshotName: string
      persistentVolumeClaimName: string
      bound: boolean
  startedAt: Time
  completedAt: Time
Field Description Default Validation
apiVersion string storage.simplyblock.io/v1alpha2
kind string VolumeGroupSnapshotOps
metadata ObjectMeta ⧉ Refer to Kubernetes API documentation for fields of metadata.
spec VolumeGroupSnapshotOpsSpec
status VolumeGroupSnapshotOpsStatus

VolumeGroupSnapshotOpsAction

Underlying type: string

VolumeGroupSnapshotOpsAction is the operation to perform on the target.

Validation: - Enum: [Restore]

Appears in: - VolumeGroupSnapshotOpsSpec

Field Description
Restore VolumeGroupSnapshotOpsActionRestore restores every member of the target's
generation into a new PersistentVolumeClaim.

VolumeGroupSnapshotOpsPhase

Underlying type: string

VolumeGroupSnapshotOpsPhase is the lifecycle phase of the operation.

Validation: - Enum: [Pending Running Succeeded Failed]

Appears in: - VolumeGroupSnapshotOpsStatus

Field Description
Pending
Running
Succeeded
Failed

VolumeGroupSnapshotOpsSpec

VolumeGroupSnapshotOpsSpec defines the requested operation. The whole spec is immutable: the object is a request.

Appears in: - VolumeGroupSnapshotOps

Example:

volumeGroupSnapshotRef: string
action: VolumeGroupSnapshotOpsAction
restore:
  namePrefix: string
  storageClassName: string
  consistencyGroup: string
  enablePartialRestore: boolean
Field Description Default Validation
volumeGroupSnapshotRef string VolumeGroupSnapshotRef names the VolumeGroupSnapshot, in this namespace,
the operation acts on. Resolved at admission: a create naming a
VolumeGroupSnapshot that does not exist is rejected.
Required: {}
action VolumeGroupSnapshotOpsAction Action is the operation to perform. Enum: [Restore]
Required: {}
restore RestoreOpsSpec Restore carries the parameters of the Restore action. Ignored for any
other action.
Optional: {}

VolumeGroupSnapshotOpsStatus

VolumeGroupSnapshotOpsStatus holds the observed state of the operation.

Appears in: - VolumeGroupSnapshotOps

Example:

phase: VolumeGroupSnapshotOpsPhase
step:
  state: VolumeGroupSnapshotOpsStep
  deadline: Time
message: string
observedGeneration: integer
membersExpected: integer
membersBound: integer
members:
  - volumeSnapshotName: string
    persistentVolumeClaimName: string
    bound: boolean
startedAt: Time
completedAt: Time
Field Description Default Validation
phase VolumeGroupSnapshotOpsPhase Phase is the high-level lifecycle phase. Enum: [Pending Running Succeeded Failed]
Optional: {}
step VolumeGroupSnapshotOpsStepSnapshot Step is the durable position of the action's state machine. Optional: {}
message string Message is the reason the phase is what it is: one sentence, replaced as
the phase moves.
Optional: {}
observedGeneration integer ObservedGeneration is the spec generation this status was computed from. Optional: {}
membersExpected integer MembersExpected is the member count of the target generation. Optional: {}
membersBound integer MembersBound is how many restored claims have bound. Optional: {}
members RestoredMemberStatus array Members records each member snapshot and its restored claim. Optional: {}
startedAt Time ⧉ StartedAt is when the operation began. Optional: {}
completedAt Time ⧉ CompletedAt is when the operation finished, successfully or not. Optional: {}

VolumeGroupSnapshotOpsStep

Underlying type: string

VolumeGroupSnapshotOpsStep is one step of a running operation. The enum is the union of every action's steps. Which steps belong to which action is declared by the action's state-machine graph rather than by this type.

Validation: - Enum: [Validating CreatingClaims WaitingForBind]

Appears in: - VolumeGroupSnapshotOpsStepSnapshot

Field Description
Validating
CreatingClaims
WaitingForBind

VolumeGroupSnapshotOpsStepSnapshot

VolumeGroupSnapshotOpsStepSnapshot is the durable position of the action's state machine: the step and the deadline it expires at.

Appears in: - VolumeGroupSnapshotOpsStatus

Example:

state: VolumeGroupSnapshotOpsStep
deadline: Time
Field Description Default Validation
state VolumeGroupSnapshotOpsStep Enum: [Validating CreatingClaims WaitingForBind]
Optional: {}
deadline Time ⧉ Optional: {}

VolumeMigrationSettings

VolumeMigrationSettings controls how volume migration and the post-migration realignment behave, not whether they happen. It is separate from VolumeAutoPlacementSettings because realignment applies to every volume move, whether it came from the auto-rebalancer, a manual migration, or a drain, so it cannot sit under the rebalancing policy that is only one of its three sources.

There is no switch here. Migration cannot be turned off: a drain, a rebalance, and a device replacement are all performed by moving volumes.

Appears in: - StorageClusterSpec

Example:

rebalancerImage: string
dataRealignment:
  interval: Duration
  minMoves: integer
Field Description Default Validation
rebalancerImage string RebalancerImage is the container image the migration path-validation Job
and the rebalancer's latency and baseline Jobs run. It must carry
nvme-cli, and for rebalancing also fio and jq.
Optional: {}
dataRealignment DataRealignmentSettings DataRealignment tunes the post-migration realignment. Optional: {}