diff --git a/.github/workflows/general.yaml b/.github/workflows/general.yaml index 51b16853..7689a5b4 100644 --- a/.github/workflows/general.yaml +++ b/.github/workflows/general.yaml @@ -29,6 +29,7 @@ jobs: - name: "Build Documentation Test" run: | ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder build dev-deployment: runs-on: ubuntu-latest @@ -46,6 +47,7 @@ jobs: sed -i -r "s|(site_url: .*)|\1dev/|g" ./mkdocs.yml cat ./mkdocs.yml ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder build - name: "Creating Deployment Artifact" run: | diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 96a05673..84492720 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -39,6 +39,7 @@ jobs: - name: "Build Documentation Test" run: | ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder build build-deploy-pr: if: github.event.action != 'closed' @@ -53,6 +54,7 @@ jobs: - name: "Build Documentation" run: | ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder build - name: "Creating Deployment Artifact" run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9af5d39d..08b45533 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,9 +37,13 @@ jobs: - name: "Lock sbcli/sbcli version" run: | [ -f ./scripts/sbcli.lock ] || echo "${{ env.RELEASE_NAME }}" > ./scripts/sbcli.lock + - name: "Lock simplyblock-operator version" + run: | + ./scripts/operator-lock.sh "${{ env.RELEASE_NAME }}" - name: "Prepare Documentation Deployment: ${{ env.RELEASE_NAME }}" run: | ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder deploy ${{ env.RELEASE_NAME }} - name: "Create Version Tag: ${{ env.RELEASE_NAME }}" run: | @@ -48,9 +52,11 @@ jobs: git config user.email github-actions@github.com git add deployment git add scripts/sbcli.lock + if [ -f scripts/operator.lock ]; then git add scripts/operator.lock; fi git commit -m "Auto-Build: ${RELEASE_NAME}" git tag ${RELEASE_NAME} git rm scripts/sbcli.lock + if [ -f scripts/operator.lock ]; then git rm scripts/operator.lock; fi git commit -m "Auto-Build: Post ${RELEASE_NAME}" git push --all git push --tags diff --git a/.github/workflows/update-release.yaml b/.github/workflows/update-release.yaml index cf43ff8b..716a28f7 100644 --- a/.github/workflows/update-release.yaml +++ b/.github/workflows/update-release.yaml @@ -40,9 +40,13 @@ jobs: - name: "Lock sbcli/sbcli version" run: | [ -f ./scripts/sbcli.lock ] || echo "${{ env.RELEASE_NAME }}" > ./scripts/sbcli.lock + - name: "Lock simplyblock-operator version" + run: | + ./scripts/operator-lock.sh "${{ env.RELEASE_NAME }}" - name: "Prepare Documentation Deployment: ${{ env.RELEASE_NAME }}" run: | ./doc-builder update-repositories + ./doc-builder gen-operator-ref ./doc-builder deploy ${{ env.RELEASE_NAME }} --force - name: "Create Version Tag: ${{ env.RELEASE_NAME }}" run: | @@ -53,9 +57,11 @@ jobs: git tag -d ${RELEASE_NAME} git add deployment git add scripts/sbcli.lock + if [ -f scripts/operator.lock ]; then git add scripts/operator.lock; fi git commit -m "Auto-Build: Rebuild ${RELEASE_NAME}" git tag ${RELEASE_NAME} git rm scripts/sbcli.lock + if [ -f scripts/operator.lock ]; then git rm scripts/operator.lock; fi git commit -m "Auto-Build: Post Rebuild ${RELEASE_NAME}" git push --all git push --tags diff --git a/.gitignore b/.gitignore index 313153eb..da7529c7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ venv .idea docs/reference/cli scripts/sbcli-repo +scripts/operator-repo docs/reference/api/openapi.json diff --git a/README.md b/README.md index 2072bc5c..ac56fb22 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,38 @@ repositories to the latest commit. The command can be run at any time to update the external repositories to the latest commit. +### Generating the Operator API Reference + +The operator API reference is generated from the Simplyblock operator Go API types. By default, the generator uses the +`simplyblock-operator` checkout created by `./doc-builder update-repositories` at `./scripts/operator-repo`. If that +checkout is not present, it falls back to a sibling checkout at `../simplyblock-manager` relative to this documentation +repository: + +```bash +./scripts/operator-reference-gen.sh +``` + +Alternatively, use the `doc-builder` wrapper, which uses the managed checkout and prints a helpful error if it is +missing: + +```bash +./doc-builder gen-operator-ref +``` + +To use a different checkout, set `OPERATOR_ROOT`: + +```bash +OPERATOR_ROOT=/path/to/simplyblock-operator ./scripts/operator-reference-gen.sh +``` + +The `simplyblock-operator` checkout is pinned the same way the `sbcli` repository is (see the release process below). A +`scripts/operator.lock` file, if present, pins the operator repository to a specific tag; otherwise the latest `HEAD` is +used. + +The CI builders regenerate the operator API reference automatically after `./doc-builder update-repositories`, so +pull request and development builds reflect the operator's latest `main`, while release builds reflect the pinned +`operator.lock` tag. The generation requires Go, which is preinstalled on the GitHub runners. + ### Serving Content Locally When building or updating the documentation, it is useful to have a local builder with live updating. Mkdocs supports @@ -461,6 +493,13 @@ After pushing the new release branch, the GitHub action builder kicks in, builds website and updates the latest symlink, creates the necessary tag for history reasons, and merges the built documentation back into the `main` branch (folder `deployment`) using an auto-generated and auto-merged pull request. +As part of the build, the `sbcli` repository is pinned to the release version via `scripts/sbcli.lock`, and the +`simplyblock-operator` repository is pinned via `scripts/operator.lock`. The operator lock is resolved automatically to +the latest operator tag whose `MAJOR.MINOR` matches the release version — for example, release `26.2.4` resolves to the +newest `v26.2.y` operator tag (the patch level may differ). If no matching operator tag exists (for example for older +releases that predate the operator), the operator lock is skipped. Both lock files are committed on the version tag and +removed again afterwards, so `main` never carries a pin. + No further action is required. ### Updating an Existing Release diff --git a/doc-builder b/doc-builder index 2bd00811..ae08873b 100755 --- a/doc-builder +++ b/doc-builder @@ -40,28 +40,48 @@ function ensure_docker_image() { exit 1 } -function update_repositories() { - if [ ! -d ./scripts/sbcli-repo ]; then - echo "Cloning sbcli repository..." - git clone https://github.com/simplyblock-io/sbcli.git ./scripts/sbcli-repo - - if [ -f ./scripts/sbcli.lock ]; then - echo "Found sbcli.lock, checking out locked reference..." - pushd ./scripts/sbcli-repo - git checkout "$(cat ../sbcli.lock)" +function update_repository() { + local name="$1" + local url="$2" + local dir="$3" + local lock="$4" + + if [ ! -d "${dir}" ]; then + echo "Cloning ${name} repository..." + git clone "${url}" "${dir}" + + if [ -f "${lock}" ]; then + local ref="$(cat "${lock}")" + echo "Found $(basename "${lock}"), checking out locked reference ${ref}..." + pushd "${dir}" + git checkout "${ref}" popd fi else - echo "Updating sbcli repository..." - pushd ./scripts/sbcli-repo + echo "Updating ${name} repository..." + pushd "${dir}" git reset --hard git pull -f - local ret=$? popd - exit ${ret} fi } +function update_repositories() { + update_repository "sbcli" \ + "https://github.com/simplyblock-io/sbcli.git" \ + "./scripts/sbcli-repo" \ + "./scripts/sbcli.lock" + + # The operator repository is pinned to the latest tag matching the sbcli + # MAJOR.MINOR version (patch level may differ). The matching tag is resolved + # during the release and stored in ./scripts/operator.lock (see release.yaml + # and scripts/operator-lock.sh). Without a lock file, the latest HEAD is used. + update_repository "simplyblock-operator" \ + "https://github.com/simplyblock/simplyblock-operator.git" \ + "./scripts/operator-repo" \ + "./scripts/operator.lock" +} + function build_image() { docker build -t ${IMAGE_NAME} . exit $? @@ -90,6 +110,16 @@ function gen_sbcli_ref() { ${DOCKER} run --rm ${terminal} -v "${PWD}":/docs --entrypoint="/usr/local/bin/python" ${IMAGE_NAME} /docs/scripts/openapi-json-gen.py } +function gen_operator_ref() { + if [ ! -d ./scripts/operator-repo ]; then + echo "The simplyblock-operator repository is not yet available. Please run ./doc-builder update-repositories and try again." + exit 1 + fi + + echo "Generating operator API reference..." + ./scripts/operator-reference-gen.sh +} + function build_css() { ${DOCKER} run --rm ${terminal} -v "${PWD}":/docs --entrypoint="/docs/scripts/build-css.sh" ${IMAGE_NAME} } @@ -221,17 +251,31 @@ function run_help() { echo " with the same name." ;; "update-repositories") - echo "Updates the sbcli repository" + echo "Updates the sbcli and simplyblock-operator repositories" echo "./doc-builder update-repositories" echo echo "If a scripts/sbcli.lock file, with a specific tag name or git hash reference" echo "exists, the sbcli git subrepository will be checked out to this specific" echo "reference. Otherwise, it will be the latest HEAD." + echo + echo "The same applies to the simplyblock-operator repository via a" + echo "scripts/operator.lock file. The operator lock is resolved during the release" + echo "to the latest operator tag matching the sbcli MAJOR.MINOR version." ;; "gen-sbcli-ref") echo "Generates the sbcli / sbctl reference documentation" echo "./doc-builder gen-sbcli-ref" ;; + "gen-operator-ref") + echo "Generates the simplyblock-operator API reference documentation" + echo "./doc-builder gen-operator-ref" + echo + echo "Regenerates docs/reference/operator-api.md from the operator repository" + echo "checked out at ./scripts/operator-repo (see ./doc-builder update-repositories)." + echo "Requires Go to be installed. When a scripts/operator.lock file exists, the" + echo "operator repository is pinned to that reference; otherwise the latest HEAD is" + echo "used." + ;; *) echo "Unknown command. No help available." ;; @@ -290,12 +334,20 @@ case "$1" in gen_sbcli_ref ;; + "gen-operator-ref") + # Test for help requested + run_help "$1" "$2" + + gen_operator_ref + ;; + *) echo "Simplyblock Documentation Builder" echo "Available commands:" echo " ./doc-builder build-image Building required docker image" echo " ./doc-builder update-repositories Initialized or updated external repository" echo " ./doc-builder gen-sbcli-ref Generates the sbcli reference pages" + echo " ./doc-builder gen-operator-ref Generates the operator API reference page" echo " ./doc-builder serve Live serving content changes" echo " ./doc-builder build Building static documentation" echo " ./doc-builder build-css Building static CSS" diff --git a/docs/deployments/kubernetes/index.md b/docs/deployments/kubernetes/index.md index 84f43a7a..fc2803e1 100644 --- a/docs/deployments/kubernetes/index.md +++ b/docs/deployments/kubernetes/index.md @@ -43,7 +43,7 @@ The operator manages the following resources: | `Device` | Manages NVMe devices on storage nodes | | `Task` | Monitors cluster tasks | -For detailed CRD documentation, see [Simplyblock Operator](../../reference/operator.md). +For detailed CRD documentation, see [Simplyblock Operator](../../reference/operator/index.md). ## Platform-Specific Notes diff --git a/docs/deployments/kubernetes/k8s-control-plane.md b/docs/deployments/kubernetes/k8s-control-plane.md index 42adb7ef..16dd1864 100644 --- a/docs/deployments/kubernetes/k8s-control-plane.md +++ b/docs/deployments/kubernetes/k8s-control-plane.md @@ -52,4 +52,4 @@ kubectl get pods -n simplyblock Once the cluster is created, proceed to [Deploy Storage Nodes](k8s-storage-plane.md) to add storage capacity and enable volume provisioning. -For a complete reference of all CRD fields, see [Simplyblock Operator](../../reference/operator.md). +For a complete reference of all CRD fields, see [Simplyblock Operator](../../reference/operator/index.md). diff --git a/docs/deployments/kubernetes/k8s-storage-plane.md b/docs/deployments/kubernetes/k8s-storage-plane.md index 2b024ae3..4c66c90a 100644 --- a/docs/deployments/kubernetes/k8s-storage-plane.md +++ b/docs/deployments/kubernetes/k8s-storage-plane.md @@ -167,7 +167,7 @@ When the status becomes `active`, the operator automatically creates a `simplybl There is no necessity to manage this secret manually. The operator keeps it up to date and removes the cluster entry when the cluster is deleted. -For a full list of configuration options see [Simplyblock Operator: StorageNodeSet](../../reference/operator.md#storagenodeset). +For a full list of configuration options see [Simplyblock Operator: StorageNodeSet](../../reference/operator/reference.md#storagenodeset). !!! warning Simplyblock exclusively owns the resources it has been allocated. It must be ensured they are sized correctly @@ -209,7 +209,7 @@ Once the pool is active, the operator automatically creates a StorageClass named `simplyblock-simplyblock-cluster-production-pool`. The StorageClass is automatically removed when the storage pool is deleted. For full details and customization options -are available at [Simplyblock Operator: Storage Pool](../../reference/operator.md#storage-pool). +are available at [Simplyblock Operator: Storage Pool](../../reference/operator/reference.md#pool). ```bash title="Check the StorageClass" kubectl get storageclass simplyblock-simplyblock-production-my-pool diff --git a/docs/maintenance-operations/operator-cluster-operations.md b/docs/maintenance-operations/operator-cluster-operations.md index 3791b982..5879ab0b 100644 --- a/docs/maintenance-operations/operator-cluster-operations.md +++ b/docs/maintenance-operations/operator-cluster-operations.md @@ -179,7 +179,7 @@ kubectl get storagenodeops migrate-worker-1 -n simplyblock -w ``` The operation progresses through sub-phases: `Preparing → Restarting → Promoting`. See -[StorageNodeOps: migrate](../reference/operator.md#migrating-a-storage-node-to-a-different-worker-migrate) +[StorageNodeOps: migrate](../reference/operator/index.md#migrating-a-storage-node-to-a-different-worker-migrate) for full details including `newSsdPcie` and `reattachVolume` options. ### Draining and Removing a Node diff --git a/docs/reference/operator.md b/docs/reference/operator.md deleted file mode 100644 index bd9078f6..00000000 --- a/docs/reference/operator.md +++ /dev/null @@ -1,817 +0,0 @@ ---- -title: "Simplyblock Operator Reference" -description: "The simplyblock Kubernetes operator manages simplyblock storage clusters, storage nodes, pools, logical volumes, and devices using Custom Resource Definitions (CRDs)." -weight: 20090 ---- - -The simplyblock Kubernetes operator provides a declarative, Kubernetes-native interface for managing simplyblock storage -infrastructure. Instead of using the CLI, administrators can define storage clusters, storage nodes, pools, and logical -volumes as Kubernetes Custom Resource Definitions (CRDs). The operator continuously reconciles the desired state with -the actual state of the simplyblock cluster. - -## Overview - -The operator manages the following Custom Resource Definitions (CRDs): - -| CRD | Short Name | Description | -|--------------------|------------|-----------------------------------------------------------------------| -| `StorageCluster` | - | Creates and manages a simplyblock storage cluster | -| `StorageNodeSet` | - | Fleet-level declarative management of storage nodes across workers | -| `StorageNode` | - | Represents a single backend storage node instance (auto-created) | -| `StorageNodeOps` | - | One-shot operational action targeting a single storage node | -| `Pool` | - | Creates and manages storage pools | -| `Lvol` | - | Manages logical volumes | -| `Device` | - | Manages NVMe devices on storage nodes | -| `Task` | - | Monitors cluster tasks and their status | -| `StorageBackup` | - | Creates a one-time backup of a PVC to S3 | -| `BackupRestore` | - | Restores a backup into a new PVC | -| `BackupPolicy` | - | Defines an automated backup schedule for PVCs | - -All CRDs use the API group `storage.simplyblock.io/v1alpha1`. - -## Storage Cluster - -The `StorageCluster` resource creates and manages a simplyblock storage cluster. - -```yaml title="Example: Create a storage cluster" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageCluster -metadata: - name: production - namespace: simplyblock -spec: - mgmtIfname: eth0 - haType: ha - stripe: - dataChunks: 2 - parityChunks: 1 - fabricType: tcp - warningThreshold: - capacity: 89 - provisionedCapacity: 250 - criticalThreshold: - capacity: 99 - provisionedCapacity: 500 -``` - -### Spec Fields - -| Field | Type | Description | -|-----------------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------| -| `mgmtIfname` | string | Management network interface (e.g., `eth0`). | -| `haType` | string | High availability type: `single` or `ha`. | -| `stripe.dataChunks` | int | Erasure coding data chunks per stripe. | -| `stripe.parityChunks` | int | Erasure coding parity chunks per stripe. | -| `fabricType` | string | NVMe-oF fabric type: `tcp`, `rdma`, or `tcp,rdma`. | -| `clientDataIfname` | string | Client-side data network interface name. | -| `enableNodeAffinity` | bool | Enable node affinity for data placement. | -| `strictNodeAntiAffinity` | bool | Enforce strict node anti-affinity for chunks. | -| `isSingleNode` | bool | Set to `true` for single-node clusters. | -| `blockSize` | int | Logical block size in bytes (`512` or `4096`). | -| `pageSizeInBlocks` | int | Page size expressed in blocks. | -| `qpairCount` | int | NVMe queue pair count per volume. | -| `maxQueueSize` | int | Maximum backend queue size. | -| `inflightIOThreshold` | int | Inflight I/O threshold before back-pressure is applied. | -| `maxFaultTolerance` | int | Maximum number of concurrent node faults tolerated. | -| `nvmfBasePort` | int | Base port for NVMe-oF services. Subsequent nodes increment from this value. | -| `rpcBasePort` | int | Base port for RPC services. | -| `snodeApiPort` | int | Storage node API port. | -| `warningThreshold.capacity` | int | Capacity warning threshold (percent). | -| `criticalThreshold.capacity` | int | Capacity critical threshold (percent). | -| `warningThreshold.provisionedCapacity` | int | Provisioned capacity warning threshold (percent). | -| `criticalThreshold.provisionedCapacity` | int | Provisioned capacity critical threshold (percent). | -| `action` | string | Lifecycle action: `activate` or `expand`. | -| `hashicorpVaultSettings.base_url` | string | Base URL of an external Hashicorp Vault or Openbao instance used to manage volume encryption keys (e.g., `https://vault.vault:8200/`). See [Securing the Control Plane: External KMS](../deployments/kubernetes/security.md#external-key-management-kms). | -| `backup.credentialsSecretRef.name` | string | Name of the Secret (in the same namespace) holding `access_key_id` and `secret_access_key`. **Required when `backup` is set**. | -| `backup.localEndpoint` | string | S3-compatible endpoint URL for backup storage. | -| `backup.snapshotBackups` | bool | Enable snapshot-based backups. | -| `backup.withCompression` | bool | Enable compression for backup data. | -| `backup.secondaryTarget` | int | Secondary backup target identifier. | -| `backup.localTesting` | bool | Enable local testing mode for backup. | - -### Auto-Managed CSI Credentials - -The cluster identifier is the `StorageCluster` resource name (`metadata.name`). The operator uses that name when -creating the backend cluster and the cluster credential Secret. - -When a `StorageCluster` is created or becomes active, the operator automatically creates or updates the -`simplyblock-csi-secret-v2` Secret in the operator's namespace with the cluster's credentials. This Secret is -consumed by the CSI driver and requires no manual management. When the cluster is deleted, the operator removes -the cluster's entry from the Secret automatically. - -### Status Fields - -| Field | Type | Description | -|-----------------------------------|--------|-------------------------------------------------------------| -| `uuid` | string | Cluster UUID assigned after creation. | -| `clusterName` | string | Cluster name, derived from `metadata.name`. | -| `nqn` | string | Cluster NVMe Qualified Name. | -| `status` | string | Current cluster lifecycle status. | -| `rebalancing` | bool | Whether cluster rebalancing is currently active. | -| `erasureCodingScheme` | string | Active erasure coding layout, for example `2x1`. | -| `secretName` | string | Name of the Kubernetes Secret holding cluster credentials. | -| `configured` | bool | Whether initial cluster setup has completed. | -| `actionStatus.action` | string | Most recently requested action name. | -| `actionStatus.state` | string | Action execution state. | -| `actionStatus.message` | string | Human-readable result or error message. | -| `actionStatus.updatedAt` | string | Timestamp of the last status transition. | -| `actionStatus.triggered` | bool | Whether the underlying backend action has been fired. | -| `actionStatus.observedGeneration` | int | Resource generation observed when this status was recorded. | - -## Storage Nodes - -Storage node management uses three separate CRDs with distinct responsibilities. Together they form a three-tier model: - -``` -StorageNodeSet ──► declares which workers to use and how to configure them - │ (fleet-level, declarative) - ▼ creates -StorageNode ──► represents one backend storage node instance - │ (per-worker, read-mostly, auto-created by the operator) - ▲ targeted by -StorageNodeOps ──► drives a single one-shot operation to completion - (shutdown / restart / suspend / resume / remove) -``` - -## StorageNodeSet - -The `StorageNodeSet` resource is the single point of configuration for a fleet of storage nodes. It declares which -Kubernetes workers to enrol, how to configure them (image versions, NUMA topology, device filtering, per-node -overrides), and how many nodes to add in parallel. - -The operator creates one `StorageNode` CR per enrolled worker (and per configured NUMA socket when -`socketsToUse` has more than one entry). Those child CRs are managed automatically and must not be created or -deleted manually. - -```yaml title="Example: Enrol three workers into a storage cluster" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageNodeSet -metadata: - name: simplyblock-node - namespace: simplyblock -spec: - clusterName: simplyblock-cluster - maxLogicalVolumeCount: 20 - partitions: 0 - corePercentage: 50 - workerNodes: - - worker-1.example.com - - worker-2.example.com - - worker-3.example.com -``` - -### Spec Fields - -| Field | Type | Description | -|-----------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------| -| `clusterName` | string | Name of the target `StorageCluster`. **Required, immutable**. | -| `clusterImage` | string | Storage-node container image override. | -| `spdkImage` | string | SPDK service container image override. | -| `spdkProxyImage` | string | SPDK proxy container image override. | -| `maxLogicalVolumeCount` | int | Maximum logical volumes per node. | -| `maxSize` | string | Maximum allocatable huge pages memory (e.g., `16G`). | -| `partitions` | int | Partitions per backend storage device. **Immutable**. | -| `mgmtIfname` | string | Management network interface. **Immutable**. | -| `dataIfname` | []string | Data-plane network interface names. | -| `corePercentage` | int | Percentage of CPU cores allocated to SPDK (0–99). | -| `reservedSystemCPU` | string | CPUs reserved for system workloads (e.g., `0,1`). | -| `enableCpuTopology` | bool | Enable topology-aware CPU scheduling. | -| `socketsToUse` | []string | NUMA sockets to deploy storage on (e.g., `["0","1"]`). | -| `nodesPerSocket` | int | Storage nodes per NUMA socket. **Immutable**. | -| `journalManager.count` | int | Journal manager count. | -| `journalManager.percentPerDevice` | int | Journal manager capacity as a percentage of each device. | -| `pcieAllowList` | []string | PCIe addresses of NVMe devices to include. | -| `pcieDenyList` | []string | PCIe addresses of NVMe devices to exclude. | -| `pcieModel` | string | Filter devices by PCI model string. | -| `deviceNames` | []string | Explicit NVMe namespace names (alternative to PCIe filtering). | -| `driveSizeRange` | string | Filter devices by capacity range (e.g., `100G-2T`). | -| `forceFormat4K` | bool | Force 4K block-size formatting. **Immutable**. | -| `skipKubeletConfiguration` | bool | Skip kubelet configuration changes during node setup. | -| `openShiftCluster` | bool | Enable OpenShift-specific behaviour. | -| `ubuntuHost` | bool | Indicate the host OS is Ubuntu. | -| `tolerations` | []Toleration | Pod tolerations applied to storage-node DaemonSet pods. | -| `workerNodes` | []string | Kubernetes worker node names to enrol. **Required, max 200**. | -| `maxParallelNodeAdds` | int | Maximum number of nodes added concurrently (default: `1`). | -| `spdkSystemMemory` | string | Memory reserved for the SPDK system allocator (e.g., `4G`). | -| `expand` | bool | Mark this set as a cluster-expansion add. | -| `nodeConfigs` | map[string]Overrides | Per-worker configuration overrides keyed by worker hostname. | -| `nodeFailureDomains` | map[string]int | Failure-domain assignment per worker (integer ≥ 1). | -| `imagePullPolicy` | string | Image pull policy: `Always`, `Never`, or `IfNotPresent`. | -| `containerResources` | ResourceRequirements | CPU/memory requests and limits for the main storage-node container. | -| `initContainerResources` | ResourceRequirements | CPU/memory requests and limits for init containers. | - -### Status Fields - -| Field | Type | Description | -|--------------------------------------|--------|----------------------------------------------------------------------------------------------------| -| `totalNodes` | int | Total number of owned `StorageNode` CRs. | -| `onlineNodes` | int | Count of nodes currently in `online` state. | -| `offlineNodes` | int | Count of nodes in `offline` state. | -| `suspendedNodes` | int | Count of nodes in `suspended` state. | -| `creatingNodes` | int | Count of nodes in `in_creation` state. | -| `removedNodes` | int | Count of nodes in `removed` state. | -| `nodes[].uuid` | string | Backend node UUID. | -| `nodes[].hostname` | string | Kubernetes node hostname. | -| `nodes[].status` | string | Backend lifecycle state. | -| `nodes[].health` | bool | Whether health checks are currently passing. | -| `nodes[].mgmtIp` | string | Management IP address. | -| `nodes[].rpcPort` | int | Node RPC service port. | -| `nodes[].lvolPort` | int | Logical volume subsystem port. | -| `nodes[].nvmfPort` | int | NVMe-oF service port. | -| `drainCoordination[].hostname` | string | Kubernetes node name being drained. | -| `drainCoordination[].activeNodeUUID` | string | Backend UUID of the storage node being shut down or restarted. | -| `drainCoordination[].phase` | string | Drain phase: `detected`, `shutdown_called`, `draining`, `restart_called`, `complete`, or `failed`. | -| `drainCoordination[].message` | string | Additional status detail or error information. | -| `drainCoordination[].startedAt` | string | Timestamp when drain coordination began for this node. | - -## StorageNode - -The `StorageNode` resource represents a single backend storage node instance. One `StorageNode` CR is created -automatically by the operator for each (worker, NUMA socket) combination declared in a `StorageNodeSet`. These -CRs are read-mostly — their spec is set at creation and is effectively immutable. - -```bash title="List all StorageNode instances" -kubectl get storagenodes -n simplyblock -``` - -```plain title="Example output" -NAME WORKER SOCKET NODEIDX UUID STATUS HEALTH AGE -simplyblock-node-worker-1.example.com-s0-n0 worker-1.example.com 0 0 a1b2c3d4-... online true 10m -simplyblock-node-worker-2.example.com-s0-n0 worker-2.example.com 0 0 b2c3d4e5-... online true 8m -simplyblock-node-worker-3.example.com-s0-n0 worker-3.example.com 0 0 c3d4e5f6-... online true 6m -``` - -### Spec Fields - -| Field | Type | Description | -|---------------------|--------|---------------------------------------------------------------------------------------| -| `storageNodeSetRef` | string | Name of the owning `StorageNodeSet`. **Required, immutable**. | -| `workerNode` | string | Kubernetes node hostname. **Required, immutable**. | -| `socketID` | string | NUMA socket identifier from `socketsToUse`. **Immutable**. | -| `nodeIndex` | int | Per-socket node index (0…nodesPerSocket-1). **Immutable**. | -| `socketIndex` | int | Global ordinal across all sockets on this worker. **Immutable**. | -| `overrides` | object | Per-node configuration overrides. See [StorageNode Overrides](#storagenode-overrides). | - -### StorageNode Overrides - -`spec.overrides` allows any field from the parent `StorageNodeSet` to be tuned on a per-node basis. Overrides win -over fleet defaults. They can be set in two ways: - -1. **Via `StorageNodeSet.spec.nodeConfigs`** — the operator propagates the matching entry to the `StorageNode` CR - automatically. -2. **Directly on a manually-created `StorageNode` CR** — useful when you need fine-grained control over a single - node, for example during expansion. - -#### Overrides Reference - -| Field | Type | Description | -|------------------------|----------|----------------------------------------------------------------------------------------------| -| `maxLogicalVolumeCount`| int | Maximum logical volumes for this node. | -| `maxSize` | string | Maximum allocatable huge pages memory (e.g., `16G`). | -| `spdkImage` | string | SPDK image override (e.g., for phased rollouts of a new image version). | -| `spdkProxyImage` | string | SPDK proxy image override. | -| `spdkSystemMemory` | string | SPDK huge-page memory allocation (e.g., `4G`, `512M`). Useful for nodes with less RAM. | -| `corePercentage` | int | Percentage of CPU cores allocated to SPDK (0–99). | -| `journalManager` | object | Journal manager tuning (`count`, `percentPerDevice`). | -| `pcieAllowList` | []string | PCIe addresses allowed for this node. | -| `pcieDenyList` | []string | PCIe addresses excluded on this node. | -| `pcieModel` | string | PCI model string filter for this node. | -| `driveSizeRange` | string | Drive size range filter (e.g., `100G-2T`). | -| `deviceNames` | []string | Explicit NVMe namespace names (e.g., `["nvme0n1","nvme1n1"]`). | -| `enableCpuTopology` | bool | Topology-aware CPU scheduling override. | -| `reservedSystemCPU` | string | CPUs reserved for system workloads (e.g., `0,1`). | -| `failureDomain` | int | Failure-domain group index (≥ 1). Required when the cluster has `enableFailureDomains=true`. | -| `expand` | bool | Mark this node as a cluster-expansion add (triggers rebalancing on the backend). | - -#### Use Cases - -**Different memory allocation per node** - -Some nodes may have less RAM. Override `spdkSystemMemory` to cap huge-page allocation: - -```yaml -# StorageNodeSet.spec.nodeConfigs -nodeConfigs: - low-ram-worker.example.com: - spdkSystemMemory: "2G" -``` - -**Failure domain assignment** - -Required when the `StorageCluster` has `enableFailureDomains: true`. Assign each worker to a domain so the -cluster can maintain fault tolerance across racks or availability zones: - -```yaml -nodeConfigs: - worker-rack-a-1.example.com: - failureDomain: 1 - worker-rack-a-2.example.com: - failureDomain: 1 - worker-rack-b-1.example.com: - failureDomain: 2 - worker-rack-b-2.example.com: - failureDomain: 2 -``` - -**Node-level volume limit** - -Limit the number of volumes on a specific node that has fewer or smaller devices: - -```yaml -nodeConfigs: - small-worker.example.com: - maxLogicalVolumeCount: 5 -``` - -**Expansion add (manual StorageNode CR)** - -When creating a `StorageNode` CR manually for cluster expansion, set `expand: true` so the backend applies -rebalancing rather than treating it as a fresh node. Combine with any other node-specific tuning: - -```yaml -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageNode -metadata: - name: simplyblock-node-vm15-expansion - namespace: simplyblock -spec: - storageNodeSetRef: simplyblock-node - workerNode: vm15.simplyblock3.localdomain - socketIndex: 0 - overrides: - expand: true - maxLogicalVolumeCount: 20 - spdkSystemMemory: "4G" - failureDomain: 2 -``` - -**Device filtering per node** - -Use different device selection strategies per node when hardware is mixed across workers: - -```yaml -nodeConfigs: - nvme-only-worker.example.com: - deviceNames: - - nvme0n1 - - nvme1n1 - pcie-filter-worker.example.com: - pcieAllowList: - - "0000:01:00.0" - - "0000:02:00.0" - driveSizeRange: "1.7T-2T" -``` - -### Status Fields - -| Field | Type | Description | -|---------------------|--------|-------------------------------------------------------------| -| `uuid` | string | Backend storage node UUID (set after provisioning). | -| `status` | string | Backend lifecycle status (`online`, `offline`, `suspended`, `in_creation`, etc.). | -| `health` | bool | Backend health flag. | -| `hostname` | string | Node hostname as reported by the backend. | -| `resources.cpu` | int | SPDK CPU cores allocated. | -| `resources.memory` | string | SPDK memory allocation. | -| `resources.volumes` | int | Current logical volume count. | -| `resources.devices` | string | Device summary (online/total). | -| `ports.management` | string | Management IP address. | -| `ports.nvmeof` | int | NVMe-oF fabric port. | -| `ports.lvol` | int | Logical volume subsystem port. | -| `ports.rpc` | int | RPC/management API port. | -| `postedAt` | string | Timestamp of the node-add POST (provisioning guard). | - -## StorageNodeOps - -The `StorageNodeOps` resource drives a single one-shot operation against one `StorageNode`. It is analogous to a -Kubernetes `Job` — the operator executes the requested action, records the outcome, and the CR is left in a -terminal state. Only one `StorageNodeOps` may be active for a given `StorageNode` at a time. - -```yaml title="Example: Restart a specific storage node" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageNodeOps -metadata: - name: restart-worker-1 - namespace: simplyblock -spec: - storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 - action: restart -``` - -```yaml title="Example: Remove (drain) a storage node" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageNodeOps -metadata: - name: drain-worker-1 - namespace: simplyblock -spec: - storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 - action: remove -``` - -### Spec Fields - -| Field | Type | Description | -|--------------------|--------|----------------------------------------------------------------------------------------------------------| -| `storageNodeRef` | string | Name of the target `StorageNode` CR. **Required, immutable**. | -| `action` | string | Operation: `shutdown`, `restart`, `suspend`, `resume`, `remove`. **Required, immutable**. | -| `force` | bool | Force execution where the backend supports it. | -| `reattachVolume` | bool | Reattach volumes during restart (`restart` only). | -| `drain.systemVolumeFilterRegex` | string | Go regex matching system volumes to exclude from migration and delete in the Verifying phase. Defaults to `^sb-fio-baseline-.*`. | - -### Status Fields - -| Field | Type | Description | -|-------------------|--------|-------------------------------------------------------------------------------------------------------| -| `phase` | string | High-level lifecycle: `Pending`, `Running`, `Succeeded`, or `Failed`. | -| `subPhase` | string | Active drain step (`remove` only): `Validating`, `Suspending`, `Migrating`, `Verifying`, `Removing`. | -| `message` | string | Human-readable state description or failure reason. | -| `volumesMigrated` | int | Number of volumes successfully migrated (`remove` only). | -| `volumesPending` | int | Number of volumes still awaiting migration (`remove` only). | -| `startedAt` | string | Operation start timestamp. | -| `completedAt` | string | Operation completion timestamp. | - -### Supported Actions - -| Action | Expected outcome after success | -|------------|-------------------------------------------------------------------| -| `shutdown` | Node transitions to `offline`. | -| `restart` | Node transitions back to `online`. | -| `suspend` | Node transitions to `suspended`. | -| `resume` | Node transitions back to `online`. | -| `remove` | Node is drained, all volumes migrated, node deleted from backend. | -| `migrate` | Node is relocated to a different Kubernetes worker, promoted. | - -### Migrating a Storage Node to a Different Worker (`migrate`) - -The `migrate` action **relocates** a storage node to a different Kubernetes worker without removing it from the -cluster. Unlike `remove`, the node retains its backend UUID, its data partitions, and its logical-volume -assignments — no `VolumeMigration` CRs are created and no volumes are moved between nodes. The backend rebalance -triggered by the final promote redistributes load automatically. - -```yaml title="Example: Relocate a storage node to a different worker" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageNodeOps -metadata: - name: migrate-worker-1 - namespace: simplyblock -spec: - storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 - action: migrate - targetWorkerNode: worker-5.example.com -``` - -**`migrate`-specific spec fields:** - -| Field | Type | Description | -|--------------------|----------|--------------------------------------------------------------------------------------------------------| -| `targetWorkerNode` | string | Kubernetes worker hostname to relocate the node to. **Required for `migrate`**, immutable. | -| `reattachVolume` | bool | Reattach volumes during the restart phase. | -| `newSsdPcie` | []string | Additional NVMe PCIe addresses to bind on the target host (passed as `new_ssd_pcie` to the backend). | - -**Sub-phases for `migrate`:** - -| SubPhase | Description | -|---------------|---------------------------------------------------------------------------------------------------------------| -| `Preparing` | Operator clones per-node config to the target worker, labels it into the storage plane, and waits until the storage-node-api pod is Ready and reachable. | -| `Restarting` | Operator issues a control-plane restart pointing at the target host. Waits for the node to leave `online` (restart started) and return to `online` (restart finished). | -| `Promoting` | Operator issues `/promote` on the relocated node, triggering a cluster rebalance. StorageNodeSet.workerNodes is updated to replace the source worker with the target. | - -### Pinned Volume Behaviour During `remove` - -PVCs annotated with `simplyblock.io/pinned-volume` affect the `remove` drain flow: - -- If the annotation value is a **valid storage node UUID** (different from the node being drained), the volume is - migrated to that specific node — drain proceeds normally. -- If the annotation value is **empty, not a UUID, or self-referencing** (pointing to the node being drained), - drain is blocked and a `PinnedVolumeBlocking` event is emitted naming the affected PVC. - -To migrate a pinned volume to a specific node, set the annotation to the target node UUID before draining: - -```bash title="Set migration target for a pinned volume" -kubectl annotate pvc -n \ - simplyblock.io/pinned-volume= --overwrite -``` - -See [Pinned Volume Migration During Node Removal](../maintenance-operations/node-drain-coordination.md#pinned-volume-migration-during-node-removal) for full details. - - -## Storage Pool - -The `Pool` resource creates and manages storage pools. When a pool becomes active, the operator automatically -creates a Kubernetes `StorageClass` named `simplyblock---`. The StorageClass is deleted -when the pool is deleted. - -```yaml title="Example: Create a storage pool" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: Pool -metadata: - name: production-pool - namespace: simplyblock -spec: - clusterName: production - capacityLimit: "10T" - qos: - iops: 100000 - throughput: - readWrite: 2048 - read: 1024 - write: 1024 -``` - -### Spec Fields - -| Field | Type | Description | -|----------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `clusterName` | string | Name of the cluster. **Required**. | -| `capacityLimit` | string | Maximum pool capacity (e.g., `10T`). | -| `qos.iops` | int | Maximum IOPS for the pool. | -| `qos.throughput.readWrite` | int | Maximum combined read/write throughput (MiB/s). | -| `qos.throughput.read` | int | Maximum read throughput (MiB/s). | -| `qos.throughput.write` | int | Maximum write throughput (MiB/s). | -| `action` | string | Pool lifecycle action. | -| `storageClassParameters` | object | Default volume parameters baked into the auto-created StorageClass. See [Quality of Service](../usage/simplyblock-csi/quality-of-service.md) for available fields. | - -### Auto-Created StorageClass - -The pool identifier is the `Pool` resource name (`metadata.name`). The operator uses that name as the backend pool -name and as the `pool_name` CSI StorageClass parameter. - -When the pool reaches an active state, the operator creates a `StorageClass` with: - -- **Name**: `simplyblock---` -- **Provisioner**: `csi.simplyblock.io` -- **VolumeBindingMode**: `WaitForFirstConsumer` -- **ReclaimPolicy**: `Delete` -- **AllowVolumeExpansion**: `true` - -The `cluster_id` and `pool_name` parameters are set automatically. Any fields specified in -`spec.storageClassParameters` are merged in as additional CSI driver parameters. - -Because Kubernetes StorageClass parameters are immutable after creation, the StorageClass is created once and -left unchanged if it already exists. To change parameters, delete the pool and recreate it with updated values. - -The StorageClass is deleted when the pool is deleted. - -### Status Fields - -| Field | Type | Description | -|----------------------------|--------|--------------------------------------------------------------| -| `uuid` | string | Backend pool UUID assigned after creation. | -| `status` | string | Backend lifecycle status. | -| `qos.host` | string | Backend host responsible for enforcing pool QoS. | -| `qos.iops` | int | Currently configured IOPS limit. | -| `qos.throughput.readWrite` | int | Currently configured combined read/write throughput (MiB/s). | -| `qos.throughput.read` | int | Currently configured read throughput (MiB/s). | -| `qos.throughput.write` | int | Currently configured write throughput (MiB/s). | - -## Logical Volume - -The `Lvol` resource manages logical volumes. It provides a read-only view of volumes in a cluster and pool. - -```yaml title="Example: List logical volumes" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: Lvol -metadata: - name: cluster-volumes - namespace: simplyblock -spec: - clusterName: production - poolName: production-pool -``` - -### Status Fields - -Each volume in the `status.lvols` list includes: - -| Field | Type | Description | -|-----------------------------|----------|-----------------------------------------------------------------------------------------------------------| -| `uuid` | string | Volume UUID. | -| `lvolName` | string | Volume name. | -| `status` | string | Backend lifecycle status. | -| `size` | string | Volume size. | -| `ha` | bool | High availability enabled. | -| `health` | bool | Whether health checks are passing. | -| `encrypted` | bool | Whether the volume is encrypted. See [Volume Encryption](../deployments/kubernetes/volume-encryption.md). | -| `erasureCodingScheme` | string | Active erasure coding layout for this volume (e.g., `2x1`). | -| `nqn` | string | NVMe Qualified Name for the volume. | -| `subsysPort` | int | NVMe subsystem listener port. | -| `namespaceID` | int | NVMe namespace identifier. | -| `poolName` | string | Storage pool name. | -| `poolUUID` | string | Storage pool UUID. | -| `nodeUUID` | []string | Node UUIDs associated with this volume. | -| `hostname` | string | Node hostname associated with the volume. | -| `pvcName` | string | Bound Kubernetes PVC name, if applicable. | -| `fabricType` | string | Storage fabric/protocol in use (`tcp` or `rdma`). | -| `clonedFromSnapshot` | string | Source snapshot ID if this volume was cloned from a snapshot. | -| `sourceSnapshotName` | string | Source snapshot name if this volume was cloned from a snapshot. | -| `qos.class` | int | Assigned QoS class identifier. | -| `qos.iops` | int | IOPS limit for this volume. | -| `qos.throughput.read` | int | Read throughput limit (MiB/s). | -| `qos.throughput.write` | int | Write throughput limit (MiB/s). | -| `qos.throughput.readWrite` | int | Combined read/write throughput limit (MiB/s). | -| `blobID` | int | Backend blob identifier. | -| `maxNamespacesPerSubsystem` | int | Maximum number of NVMe namespaces per subsystem. | - -### Snapshot Cloning - -When a volume is cloned from a snapshot, the `clonedFromSnapshot` and `sourceSnapshotName` fields in its status entry identify the origin. These fields are read-only and set by the backend at creation time — they cannot be specified in the `Lvol` spec. - -To see which volumes in a pool are snapshot clones: - -```bash -kubectl get simplyblocklvol cluster-volumes -n simplyblock -o jsonpath='{.status.lvols[?(@.clonedFromSnapshot!="")].lvolName}' -``` - -## Device - -The `Device` resource manages NVMe devices on storage nodes. - -```yaml title="Example: List devices" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: Device -metadata: - name: cluster-devices - namespace: simplyblock -spec: - clusterName: production -``` - -### Actions - -To perform actions on a specific device, set the `action`, `nodeUUID`, and `deviceID` fields: - -| Action | Description | -|-----------|------------------------------| -| `remove` | Remove a device from a node | -| `restart` | Restart a device on a node | - -### Status Fields - -| Field | Type | Description | -|--------------------------------------------|--------|----------------------------------------------------------------------| -| `nodes[].nodeUUID` | string | Backend UUID of the storage node. | -| `nodes[].devices[].uuid` | string | Backend device UUID. | -| `nodes[].devices[].status` | string | Backend lifecycle status of the device. | -| `nodes[].devices[].health` | string | Backend health indicator for the device. | -| `nodes[].devices[].model` | string | Reported device model. | -| `nodes[].devices[].size` | string | Formatted device capacity. | -| `actionStatus.action` | string | Most recently requested action name. | -| `actionStatus.nodeUUID` | string | Target node UUID for the action. | -| `actionStatus.state` | string | Action execution state. | -| `actionStatus.message` | string | Human-readable result or error message. | -| `actionStatus.updatedAt` | string | Timestamp of the last status transition. | -| `actionStatus.triggered` | bool | Whether the underlying backend action has been fired. | -| `actionStatus.observedGeneration` | int | Resource generation observed when this status was recorded. | - -## Task - -The `Task` resource provides visibility into cluster tasks (migrations, rebalancing, etc.). - -```yaml title="Example: Monitor tasks" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: Task -metadata: - name: cluster-tasks - namespace: simplyblock -spec: - clusterName: production - taskID: "abc123" # optional: filter to a specific task -``` - -### Spec Fields - -| Field | Type | Description | -|---------------|--------|----------------------------------------------------------------------| -| `clusterName` | string | Target storage cluster name. **Required**. | -| `taskID` | string | Filter results to a specific backend task UUID. | - -### Status Fields - -| Field | Type | Description | -|------------------------|--------|------------------------------------------------------| -| `tasks[].uuid` | string | Backend task UUID. | -| `tasks[].taskType` | string | Backend task function or type name. | -| `tasks[].taskStatus` | string | Backend lifecycle status for the task. | -| `tasks[].taskResult` | string | Backend result payload or message. | -| `tasks[].retried` | int | Number of retry attempts made for the task. | -| `tasks[].canceled` | bool | Whether the task was canceled. | - -## StorageBackup - -The `StorageBackup` resource creates a one-time backup of a PVC to the S3-compatible storage endpoint configured -in the `StorageCluster`. For backup configuration prerequisites, see -[Backup and Recovery](../usage/backup-recovery.md#kubernetes-crd-operations). - -```yaml title="Example: Create a PVC backup" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: StorageBackup -metadata: - name: my-backup - namespace: simplyblock -spec: - clusterName: production - pvcRef: - name: my-pvc -``` - -### Spec Fields - -| Field | Type | Description | -|---------------|--------|------------------------------------------------------| -| `clusterName` | string | Name of the target StorageCluster. **Required**. | -| `pvcRef.name` | string | Name of the PVC to back up. **Required**. | - -### Status Fields - -| Field | Type | Description | -|------------|--------|-------------------------------------------------------------| -| `phase` | string | Current phase: `InProgress` or `Done`. | -| `pvc` | string | Name of the source PVC. | -| `backupID` | string | Backend backup identifier assigned after the backup starts. | -| `snapshot` | string | Name of the snapshot used for the backup. | - -## BackupRestore - -The `BackupRestore` resource restores a `StorageBackup` into a new PVC. The backup may be directed to a -different pool or storage node, but must be restored within the same namespace as the `BackupRestore` object. - -```yaml title="Example: Restore a backup to a new PVC" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: BackupRestore -metadata: - name: my-restore - namespace: simplyblock -spec: - clusterName: production - backupRef: - name: my-backup - pvcTemplate: - metadata: - name: restored-pvc - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi -``` - -### Spec Fields - -| Field | Type | Description | -|-----------------------------|--------|---------------------------------------------------------------------------------| -| `clusterName` | string | Name of the target StorageCluster. **Required**. | -| `backupRef.name` | string | Name of the `StorageBackup` to restore from. **Required**. | -| `targetPool` | string | Pool to restore into. Defaults to the source backup PVC's pool. | -| `targetNode` | string | Storage node to restore to. Defaults to the node that held the original backup. | -| `pvcTemplate.metadata.name` | string | Name of the new PVC to create. **Required**. | -| `pvcTemplate.spec` | object | PVC spec including `accessModes` and `resources`. | - -### Status Fields - -| Field | Type | Description | -|----------|--------|-------------------------------------------------------| -| `phase` | string | Current phase: `InProgress`, `PVCBinding`, or `Done`. | -| `backup` | string | Name of the source `StorageBackup`. | -| `pvc` | string | Name of the newly created PVC. | - -!!! warning - `BackupRestore` can only restore a PVC to the same namespace as the restore object. - -## BackupPolicy - -The `BackupPolicy` resource defines an automated backup schedule with retention settings. Policies are attached -to PVCs using the `simplybk/backup-policy` Kubernetes annotation, which causes `StorageBackup` objects to be -created automatically on schedule. Removing the annotation detaches the policy; updating it switches the PVC to -the new policy. - -```yaml title="Example: Create a backup policy" -apiVersion: storage.simplyblock.io/v1alpha1 -kind: BackupPolicy -metadata: - name: my-policy - namespace: simplyblock -spec: - clusterName: production - maxVersions: 10 - maxAge: "7d" - schedule: "15m,4 60m,11 24h,7" -``` - -Attach the policy to a PVC: - -```bash title="Attach a backup policy to a PVC" -kubectl annotate pvc my-pvc -n simplyblock simplybk/backup-policy=my-policy -``` - -### Spec Fields - -| Field | Type | Description | -|---------------|--------|-------------------------------------------------------------------| -| `clusterName` | string | Name of the target StorageCluster. **Required**. | -| `maxVersions` | int | Maximum number of backup versions to retain. | -| `maxAge` | string | Maximum backup age before cleanup (e.g., `7d`, `12h`). | -| `schedule` | string | Tiered backup schedule as space-separated `interval,count` pairs. | - -The schedule format is a space-separated list of `interval,count` pairs. For example, `15m,4 60m,11 24h,7` means: -take a backup every 15 minutes (keep the 4 most recent), every 60 minutes (keep 11), and every 24 hours (keep 7). diff --git a/docs/reference/operator/index.md b/docs/reference/operator/index.md new file mode 100644 index 00000000..7ac025c5 --- /dev/null +++ b/docs/reference/operator/index.md @@ -0,0 +1,463 @@ +--- +title: "Simplyblock Operator" +description: "The simplyblock Kubernetes operator manages simplyblock storage clusters, storage nodes, pools, logical volumes, and devices using Custom Resource Definitions (CRDs)." +weight: 20090 +--- + +The simplyblock Kubernetes operator provides a declarative, Kubernetes-native interface for managing simplyblock storage +infrastructure. Instead of using the CLI, administrators can define storage clusters, storage nodes, pools, and logical +volumes as Kubernetes Custom Resource Definitions (CRDs). The operator continuously reconciles the desired state with +the actual state of the simplyblock cluster. + +## Overview + +The operator manages the following Custom Resource Definitions (CRDs): + +| CRD | Short Name | Description | +|-------------------------------------------------|------------|-----------------------------------------------------------------------| +| [`StorageCluster`](reference.md#storagecluster) | - | Creates and manages a simplyblock storage cluster | +| [`StorageNodeSet`](reference.md#storagenodeset) | - | Fleet-level declarative management of storage nodes across workers | +| [`StorageNode`](reference.md#storagenode) | - | Represents a single backend storage node instance (auto-created) | +| [`StorageNodeOps`](reference.md#storagenodeops) | - | One-shot operational action targeting a single storage node | +| [`Pool`](reference.md#pool) | - | Creates and manages storage pools | +| [`Task`](reference.md#task) | - | Monitors cluster tasks and their status | +| [`StorageBackup`](reference.md#storagebackup) | - | Creates a one-time backup of a PVC to S3 | +| [`BackupRestore`](reference.md#backuprestore) | - | Restores a backup into a new PVC | +| [`BackupPolicy`](reference.md#backuppolicy) | - | Defines an automated backup schedule for PVCs | + +All CRDs use the API group `storage.simplyblock.io/v1alpha1`. + +For the complete generated field reference, see [Simplyblock Operator Reference](reference.md). + +## Auto-Managed CSI Credentials + +The cluster identifier is the `StorageCluster` resource name (`metadata.name`). The operator uses that name when +creating the backend cluster and the cluster credential Secret. + +When a `StorageCluster` is created or becomes active, the operator automatically creates or updates the +`simplyblock-csi-secret-v2` Secret in the operator's namespace with the cluster's credentials. This Secret is +consumed by the CSI driver and requires no manual management. When the cluster is deleted, the operator removes +the cluster's entry from the Secret automatically. + +## Storage Nodes + +Storage node management uses three separate CRDs with distinct responsibilities. Together they form a three-tier model: + +``` +StorageNodeSet ──► declares which workers to use and how to configure them + │ (fleet-level, declarative) + ▼ creates +StorageNode ──► represents one backend storage node instance + │ (per-worker, read-mostly, auto-created by the operator) + ▲ targeted by +StorageNodeOps ──► drives a single one-shot operation to completion + (shutdown / restart / suspend / resume / remove) +``` + +## StorageNodeSet + +The `StorageNodeSet` resource is the single point of configuration for a fleet of storage nodes. It declares which +Kubernetes workers to enrol, how to configure them (image versions, NUMA topology, device filtering, per-node +overrides), and how many nodes to add in parallel. + +The operator creates one `StorageNode` CR per enrolled worker (and per configured NUMA socket when +`socketsToUse` has more than one entry). Those child CRs are managed automatically and must not be created or +deleted manually. + +```yaml title="Example: Enrol three workers into a storage cluster" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeSet +metadata: + name: simplyblock-node + namespace: simplyblock +spec: + clusterName: simplyblock-cluster + maxLogicalVolumeCount: 20 + partitions: 0 + corePercentage: 50 + workerNodes: + - worker-1.example.com + - worker-2.example.com + - worker-3.example.com +``` + +The complete set of `StorageNodeSet` fields is available in [StorageNodeSet reference](reference.md#storagenodeset). + +## StorageNode + +The `StorageNode` resource represents a single backend storage node instance. One `StorageNode` CR is created +automatically by the operator for each (worker, NUMA socket) combination declared in a `StorageNodeSet`. These +CRs are read-mostly — their spec is set at creation and is effectively immutable. + +```bash title="List all StorageNode instances" +kubectl get storagenodes -n simplyblock +``` + +```plain title="Example output" +NAME WORKER SOCKET NODEIDX UUID STATUS HEALTH AGE +simplyblock-node-worker-1.example.com-s0-n0 worker-1.example.com 0 0 a1b2c3d4-... online true 10m +simplyblock-node-worker-2.example.com-s0-n0 worker-2.example.com 0 0 b2c3d4e5-... online true 8m +simplyblock-node-worker-3.example.com-s0-n0 worker-3.example.com 0 0 c3d4e5f6-... online true 6m +``` + +### StorageNode Overrides + +`spec.overrides` allows any field from the parent `StorageNodeSet` to be tuned on a per-node basis. Overrides win +over fleet defaults. They can be set in two ways: + +1. **Via `StorageNodeSet.spec.nodeConfigs`** — the operator propagates the matching entry to the `StorageNode` CR + automatically. +2. **Directly on a manually-created `StorageNode` CR** — useful when you need fine-grained control over a single + node, for example during expansion. + +#### Overrides Reference + +| Field | Type | Description | +|-------------------------|----------|----------------------------------------------------------------------------------------------| +| `maxLogicalVolumeCount` | int | Maximum logical volumes for this node. | +| `maxSize` | string | Maximum allocatable huge pages memory (e.g., `16G`). | +| `spdkImage` | string | SPDK image override (e.g., for phased rollouts of a new image version). | +| `spdkProxyImage` | string | SPDK proxy image override. | +| `spdkSystemMemory` | string | SPDK huge-page memory allocation (e.g., `4G`, `512M`). Useful for nodes with less RAM. | +| `corePercentage` | int | Percentage of CPU cores allocated to SPDK (0–99). | +| `journalManager` | object | Journal manager tuning (`count`, `percentPerDevice`). | +| `pcieAllowList` | []string | PCIe addresses allowed for this node. | +| `pcieDenyList` | []string | PCIe addresses excluded on this node. | +| `pcieModel` | string | PCI model string filter for this node. | +| `driveSizeRange` | string | Drive size range filter (e.g., `100G-2T`). | +| `deviceNames` | []string | Explicit NVMe namespace names (e.g., `["nvme0n1","nvme1n1"]`). | +| `enableCpuTopology` | bool | Topology-aware CPU scheduling override. | +| `reservedSystemCPU` | string | CPUs reserved for system workloads (e.g., `0,1`). | +| `failureDomain` | int | Failure-domain group index (≥ 1). Required when the cluster has `enableFailureDomains=true`. | +| `expand` | bool | Mark this node as a cluster-expansion add (triggers rebalancing on the backend). | + +#### Use Cases + +**Different memory allocation per node** + +Some nodes may have less RAM. Override `spdkSystemMemory` to cap huge-page allocation: + +```yaml +# StorageNodeSet.spec.nodeConfigs +nodeConfigs: + low-ram-worker.example.com: + spdkSystemMemory: "2G" +``` + +**Failure domain assignment** + +Required when the `StorageCluster` has `enableFailureDomains: true`. Assign each worker to a domain so the +cluster can maintain fault tolerance across racks or availability zones: + +```yaml +nodeConfigs: + worker-rack-a-1.example.com: + failureDomain: 1 + worker-rack-a-2.example.com: + failureDomain: 1 + worker-rack-b-1.example.com: + failureDomain: 2 + worker-rack-b-2.example.com: + failureDomain: 2 +``` + +**Node-level volume limit** + +Limit the number of volumes on a specific node that has fewer or smaller devices: + +```yaml +nodeConfigs: + small-worker.example.com: + maxLogicalVolumeCount: 5 +``` + +**Expansion add (manual StorageNode CR)** + +When creating a `StorageNode` CR manually for cluster expansion, set `expand: true` so the backend applies +rebalancing rather than treating it as a fresh node. Combine with any other node-specific tuning: + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNode +metadata: + name: simplyblock-node-vm15-expansion + namespace: simplyblock +spec: + storageNodeSetRef: simplyblock-node + workerNode: vm15.simplyblock3.localdomain + socketIndex: 0 + overrides: + expand: true + maxLogicalVolumeCount: 20 + spdkSystemMemory: "4G" + failureDomain: 2 +``` + +**Device filtering per node** + +Use different device selection strategies per node when hardware is mixed across workers: + +```yaml +nodeConfigs: + nvme-only-worker.example.com: + deviceNames: + - nvme0n1 + - nvme1n1 + pcie-filter-worker.example.com: + pcieAllowList: + - "0000:01:00.0" + - "0000:02:00.0" + driveSizeRange: "1.7T-2T" +``` + +The complete set of `StorageNode` fields is available in [StorageNode reference](reference.md#storagenode). + +## StorageNodeOps + +The `StorageNodeOps` resource drives a single one-shot operation against one `StorageNode`. It is analogous to a +Kubernetes `Job` — the operator executes the requested action, records the outcome, and the CR is left in a +terminal state. Only one `StorageNodeOps` may be active for a given `StorageNode` at a time. + +```yaml title="Example: Restart a specific storage node" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeOps +metadata: + name: restart-worker-1 + namespace: simplyblock +spec: + storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 + action: restart +``` + +```yaml title="Example: Remove (drain) a storage node" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeOps +metadata: + name: drain-worker-1 + namespace: simplyblock +spec: + storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 + action: remove +``` + +### Supported Actions + +| Action | Expected outcome after success | +|------------|-------------------------------------------------------------------| +| `shutdown` | Node transitions to `offline`. | +| `restart` | Node transitions back to `online`. | +| `suspend` | Node transitions to `suspended`. | +| `resume` | Node transitions back to `online`. | +| `remove` | Node is drained, all volumes migrated, node deleted from backend. | +| `migrate` | Node is relocated to a different Kubernetes worker, promoted. | + +The complete set of `StorageNodeOps` fields is available in [StorageNodeOps reference](reference.md#storagenodeops). + +## Migrating a Storage Node to a Different Worker (`migrate`) + +The `migrate` action **relocates** a storage node to a different Kubernetes worker without removing it from the +cluster. Unlike `remove`, the node retains its backend UUID, its data partitions, and its logical-volume +assignments — no `VolumeMigration` CRs are created and no volumes are moved between nodes. The backend rebalance +triggered by the final promote redistributes load automatically. + +```yaml title="Example: Relocate a storage node to a different worker" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeOps +metadata: + name: migrate-worker-1 + namespace: simplyblock +spec: + storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0 + action: migrate + targetWorkerNode: worker-5.example.com +``` + +**`migrate`-specific spec fields:** + +| Field | Type | Description | +|--------------------|----------|------------------------------------------------------------------------------------------------------| +| `targetWorkerNode` | string | Kubernetes worker hostname to relocate the node to. **Required for `migrate`**, immutable. | +| `reattachVolume` | bool | Reattach volumes during the restart phase. | +| `newSsdPcie` | []string | Additional NVMe PCIe addresses to bind on the target host (passed as `new_ssd_pcie` to the backend). | + +**Sub-phases for `migrate`:** + +| SubPhase | Description | +|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Preparing` | Operator clones per-node config to the target worker, labels it into the storage plane, and waits until the storage-node-api pod is Ready and reachable. | +| `Restarting` | Operator issues a control-plane restart pointing at the target host. Waits for the node to leave `online` (restart started) and return to `online` (restart finished). | +| `Promoting` | Operator issues `/promote` on the relocated node, triggering a cluster rebalance. StorageNodeSet.workerNodes is updated to replace the source worker with the target. | + +### Pinned Volume Behavior During `remove` + +PVCs annotated with `simplyblock.io/pinned-volume` affect the `remove` drain flow: + +- If the annotation value is a **valid storage node UUID** (different from the node being drained), the volume is + migrated to that specific node — drain proceeds normally. +- If the annotation value is **empty, not a UUID, or self-referencing** (pointing to the node being drained), + drain is blocked and a `PinnedVolumeBlocking` event is emitted naming the affected PVC. + +To migrate a pinned volume to a specific node, set the annotation to the target node UUID before draining: + +```bash title="Set migration target for a pinned volume" +kubectl annotate pvc -n \ + simplyblock.io/pinned-volume= --overwrite +``` + +See [Pinned Volume Migration During Node Removal](../../maintenance-operations/node-drain-coordination.md#pinned-volume-migration-during-node-removal) for full details. + + +## Storage Pool + +The `Pool` resource creates and manages storage pools. When a pool becomes active, the operator automatically +creates a Kubernetes `StorageClass` named `simplyblock---`. The StorageClass is deleted +when the pool is deleted. + +```yaml title="Example: Create a storage pool" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: Pool +metadata: + name: production-pool + namespace: simplyblock +spec: + clusterName: production + capacityLimit: "10T" + qos: + iops: 100000 + throughput: + readWrite: 2048 + read: 1024 + write: 1024 +``` + +The complete set of `Pool` fields is available in [Pool reference](reference.md#pool). + +### Auto-Created StorageClass + +The pool identifier is the `Pool` resource name (`metadata.name`). The operator uses that name as the backend pool +name and as the `pool_name` CSI StorageClass parameter. + +When the pool reaches an active state, the operator creates a `StorageClass` with: + +- **Name**: `simplyblock---` +- **Provisioner**: `csi.simplyblock.io` +- **VolumeBindingMode**: `WaitForFirstConsumer` +- **ReclaimPolicy**: `Delete` +- **AllowVolumeExpansion**: `true` + +The `cluster_id` and `pool_name` parameters are set automatically. Any fields specified in +`spec.storageClassParameters` are merged in as additional CSI driver parameters. + +Because Kubernetes StorageClass parameters are immutable after creation, the StorageClass is created once and +left unchanged if it already exists. To change parameters, delete the pool and recreate it with updated values. + +The StorageClass is deleted when the pool is deleted. + +### Snapshot Cloning + +When a volume is cloned from a snapshot, the `clonedFromSnapshot` and `sourceSnapshotName` fields in its status entry identify the origin. These fields are read-only and set by the backend at creation time — they cannot be specified in the `Lvol` spec. + +To see which volumes in a pool are snapshot clones: + +```bash +kubectl get simplyblocklvol cluster-volumes -n simplyblock -o jsonpath='{.status.lvols[?(@.clonedFromSnapshot!="")].lvolName}' +``` + +## Task + +The `Task` resource provides visibility into cluster tasks (migrations, rebalancing, etc.). + +```yaml title="Example: Monitor tasks" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: Task +metadata: + name: cluster-tasks + namespace: simplyblock +spec: + clusterName: production + taskID: "abc123" # optional: filter to a specific task +``` + +The complete set of `Task` fields is available in [Task reference](reference.md#task). + +## StorageBackup + +The `StorageBackup` resource creates a one-time backup of a PVC to the S3-compatible storage endpoint configured +in the `StorageCluster`. For backup configuration prerequisites, see +[Backup and Recovery](../../usage/backup-recovery.md#kubernetes-crd-operations). + +```yaml title="Example: Create a PVC backup" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageBackup +metadata: + name: my-backup + namespace: simplyblock +spec: + clusterName: production + pvcRef: + name: my-pvc +``` + +The complete set of `StorageBackup` fields is available in [StorageBackup reference](reference.md#storagebackup). + +## BackupRestore + +The `BackupRestore` resource restores a `StorageBackup` into a new PVC. The backup may be directed to a +different pool or storage node, but must be restored within the same namespace as the `BackupRestore` object. + +```yaml title="Example: Restore a backup to a new PVC" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: BackupRestore +metadata: + name: my-restore + namespace: simplyblock +spec: + clusterName: production + backupRef: + name: my-backup + pvcTemplate: + metadata: + name: restored-pvc + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +!!! warning + `BackupRestore` can only restore a PVC to the same namespace as the restore object. + +The complete set of `BackupRestore` fields is available in [BackupRestore reference](reference.md#backuprestore). + +## BackupPolicy + +The `BackupPolicy` resource defines an automated backup schedule with retention settings. Policies are attached +to PVCs using the `simplybk/backup-policy` Kubernetes annotation, which causes `StorageBackup` objects to be +created automatically on schedule. Removing the annotation detaches the policy; updating it switches the PVC to +the new policy. + +```yaml title="Example: Create a backup policy" +apiVersion: storage.simplyblock.io/v1alpha1 +kind: BackupPolicy +metadata: + name: my-policy + namespace: simplyblock +spec: + clusterName: production + maxVersions: 10 + maxAge: "7d" + schedule: "15m,4 60m,11 24h,7" +``` + +Attach the policy to a PVC: + +```bash title="Attach a backup policy to a PVC" +kubectl annotate pvc my-pvc -n simplyblock simplybk/backup-policy=my-policy +``` + +The schedule format is a space-separated list of `interval,count` pairs. For example, `15m,4 60m,11 24h,7` means: +take a backup every 15 minutes (keep the 4 most recent), every 60 minutes (keep 11), and every 24 hours (keep 7). + +The complete set of `BackupPolicy` fields is available in [BackupPolicy reference](reference.md#backuppolicy). diff --git a/docs/reference/operator/reference.md b/docs/reference/operator/reference.md new file mode 100644 index 00000000..8ff552f3 --- /dev/null +++ b/docs/reference/operator/reference.md @@ -0,0 +1,3310 @@ +--- +title: "Simplyblock Operator Reference" +description: "Reference for Simplyblock operator Custom Resource Definitions (CRDs)." +weight: 20091 +--- + + + +# API Reference + +## Packages +- [storage.simplyblock.io/v1alpha1](#storagesimplyblockiov1alpha1) + + +## storage.simplyblock.io/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the simplyblock v1alpha1 API group. + +### Resource Types +- [BackupImport](#backupimport) +- [BackupPolicy](#backuppolicy) +- [BackupRestore](#backuprestore) +- [ControlPlane](#controlplane) +- [Pool](#pool) +- [SnapshotReplication](#snapshotreplication) +- [StorageBackup](#storagebackup) +- [StorageCluster](#storagecluster) +- [StorageNode](#storagenode) +- [StorageNodeOps](#storagenodeops) +- [StorageNodeSet](#storagenodeset) +- [Task](#task) +- [VolumeMigration](#volumemigration) + + + +#### ActionStatus + + + + + + + +_Appears in:_ +- [StorageClusterStatus](#storageclusterstatus) + +_Example:_ + +```yaml +action: string +nodeUUID: string +state: string +message: string +updatedAt: Time +observedGeneration: integer +triggered: boolean +subPhase: string +volumesMigrated: integer +volumesPending: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `action` _string_ | Action is the requested action name. | | | +| `nodeUUID` _string_ | NodeUUID is the target node UUID for the action. | | | +| `state` _string_ | | | | +| `message` _string_ | Message is a human-readable action result or error. | | | +| `updatedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | UpdatedAt is the timestamp of the last status transition. | | | +| `observedGeneration` _integer_ | ObservedGeneration is the resource generation observed by this status. | | | +| `triggered` _boolean_ | Triggered indicates whether the underlying backend action has been fired. | | | +| `subPhase` _string_ | SubPhase tracks the active drain step within the remove action. | | Enum: [Validating Suspending Migrating Verifying Removing]
Optional: \{\}
| +| `volumesMigrated` _integer_ | VolumesMigrated is the count of volumes successfully migrated so far. | | Optional: \{\}
| +| `volumesPending` _integer_ | VolumesPending is the count of volumes still awaiting migration. | | Optional: \{\}
| + + +#### AttachedLvol + + + +AttachedLvol records a single PVC-to-lvol attachment managed by this policy. + + + +_Appears in:_ +- [BackupPolicyStatus](#backuppolicystatus) + +_Example:_ + +```yaml +pvcName: string +pvcNamespace: string +lvolID: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `pvcName` _string_ | PVCName is the name of the PVC. | | | +| `pvcNamespace` _string_ | PVCNamespace is the namespace of the PVC. | | | +| `lvolID` _string_ | LvolID is the Simplyblock logical volume UUID that this policy is attached to. | | | + + +#### BackupCredentialsSecretRef + + + + + + + +_Appears in:_ +- [BackupSpec](#backupspec) + +_Example:_ + +```yaml +name: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name is the name of the Secret in the same namespace as the cluster CR. | | | + + +#### BackupImport + + + +BackupImport imports a completed backup from a source cluster into a target cluster, +creating a StorageBackup CR that can be referenced by a BackupRestore. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: BackupImport +metadata: + name: string +spec: + sourceClusterName: string + sourceBackupID: '^[a-zA-Z0-9_-]{1,128}$' + targetClusterName: string +status: + phase: string + message: string + sourceClusterUUID: string + targetClusterUUID: string + importedBackupID: string + storageBackupRef: string + completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `BackupImport` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[BackupImportSpec](#backupimportspec)_ | spec defines the desired state of BackupImport | | Required: \{\}
| +| `status` _[BackupImportStatus](#backupimportstatus)_ | status defines the observed state of BackupImport | | Optional: \{\}
| + + +#### BackupImportSpec + + + +BackupImportSpec defines the desired state of BackupImport. + + + +_Appears in:_ +- [BackupImport](#backupimport) + +_Example:_ + +```yaml +sourceClusterName: string +sourceBackupID: '^[a-zA-Z0-9_-]{1,128}$' +targetClusterName: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `sourceClusterName` _string_ | SourceClusterName is the StorageCluster CR name of the cluster that owns the backup. | | | +| `sourceBackupID` _string_ | SourceBackupID is the UUID of the backup on the source cluster to import. | | Pattern: `^[a-zA-Z0-9_-]\{1,128\}$`
| +| `targetClusterName` _string_ | TargetClusterName is the StorageCluster CR name of the cluster to import into. | | | + + +#### BackupImportStatus + + + +BackupImportStatus defines the observed state of BackupImport. + + + +_Appears in:_ +- [BackupImport](#backupimport) + +_Example:_ + +```yaml +phase: string +message: string +sourceClusterUUID: string +targetClusterUUID: string +importedBackupID: string +storageBackupRef: string +completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is the high-level lifecycle shown in kubectl output. | | | +| `message` _string_ | Message contains the latest reconciliation detail or error. | | | +| `sourceClusterUUID` _string_ | SourceClusterUUID is the resolved UUID of the source cluster. | | | +| `targetClusterUUID` _string_ | TargetClusterUUID is the resolved UUID of the target cluster. | | | +| `importedBackupID` _string_ | ImportedBackupID is the backup UUID after successful import into the target cluster. | | | +| `storageBackupRef` _string_ | StorageBackupRef is the name of the StorageBackup CR created in the target namespace
after a successful import. This CR can be referenced directly in a BackupRestore. | | | +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CompletedAt is when the import completed. | | | + + +#### BackupPolicy + + + +BackupPolicy is the Schema for the backuppolicies API. + +A BackupPolicy defines retention and scheduling parameters for Simplyblock +backups. To apply a policy to a PVC, annotate the PVC with: + + simplybk/backup-policy: + +The BackupPolicy must be in the same namespace as the annotated PVC. +The controller attaches and detaches the policy in the Simplyblock backend +whenever the annotation is added or removed. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: BackupPolicy +metadata: + name: string +spec: + clusterName: string + maxVersions: integer + maxAge: '^[1-9]\d*[mhdw]$' + schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$' +status: + phase: string + message: string + clusterUUID: string + policyID: string + attachedLvols: + - pvcName: string + pvcNamespace: string + lvolID: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `BackupPolicy` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[BackupPolicySpec](#backuppolicyspec)_ | spec defines the desired state of BackupPolicy | | Required: \{\}
| +| `status` _[BackupPolicyStatus](#backuppolicystatus)_ | status defines the observed state of BackupPolicy | | Optional: \{\}
| + + +#### BackupPolicySpec + + + +BackupPolicySpec defines the desired state of BackupPolicy. + + + +_Appears in:_ +- [BackupPolicy](#backuppolicy) + +_Example:_ + +```yaml +clusterName: string +maxVersions: integer +maxAge: '^[1-9]\d*[mhdw]$' +schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$' +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `maxVersions` _integer_ | MaxVersions is the maximum number of completed backup versions to retain.
When exceeded, the oldest backup is merged into the second-oldest. | | Optional: \{\}
| +| `maxAge` _string_ | MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m").
Backups older than this are merged. Accepts m, h, d, w suffixes. | | Pattern: `^[1-9]\d*[mhdw]$`
Optional: \{\}
| +| `schedule` _string_ | Schedule defines the tiered backup schedule as a space-separated list of
interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7").
Intervals must be strictly increasing. Supported units: m, h, d, w. | | Pattern: `^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$`
Optional: \{\}
| + + +#### BackupPolicyStatus + + + +BackupPolicyStatus defines the observed state of BackupPolicy. + + + +_Appears in:_ +- [BackupPolicy](#backuppolicy) + +_Example:_ + +```yaml +phase: string +message: string +clusterUUID: string +policyID: string +attachedLvols: + - pvcName: string + pvcNamespace: string + lvolID: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is the high-level lifecycle state of the policy. | | | +| `message` _string_ | Message contains the latest reconciliation detail or error. | | | +| `clusterUUID` _string_ | ClusterUUID is the resolved backend cluster UUID. | | | +| `policyID` _string_ | PolicyID is the UUID assigned to this policy by the Simplyblock backend. | | | +| `attachedLvols` _[AttachedLvol](#attachedlvol) array_ | AttachedLvols lists the PVCs (and their lvol IDs) currently attached to
this policy in the Simplyblock backend. The controller uses this to detect
and reconcile annotation additions and removals. | | | + + +#### BackupRef + + + +BackupRef identifies the StorageBackup to restore from, scoped to the same namespace. + + + +_Appears in:_ +- [BackupRestoreSpec](#backuprestorespec) + +_Example:_ + +```yaml +name: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name is the StorageBackup resource name. | | | + + +#### BackupRestore + + + +BackupRestore is the Schema for the backuprestores API. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: BackupRestore +metadata: + name: string +spec: + clusterName: string + backupRef: + name: string + targetPool: string + targetNode: string + pvcTemplate: + metadata: + name: string + spec: PersistentVolumeClaimSpec +status: + phase: string + message: string + clusterUUID: string + backupID: string + sourceLvolID: string + fsType: string + poolName: string + poolUUID: string + restoredLvolID: string + pvName: string + pvcName: string + pvcNamespace: string + sourceClusterUUID: string + sourceSwitchedAt: Time + startedAt: Time + completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `BackupRestore` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[BackupRestoreSpec](#backuprestorespec)_ | spec defines the desired state of BackupRestore | | Required: \{\}
| +| `status` _[BackupRestoreStatus](#backuprestorestatus)_ | status defines the observed state of BackupRestore | | Optional: \{\}
| + + +#### BackupRestoreSpec + + + +BackupRestoreSpec defines the desired state of BackupRestore. + + + +_Appears in:_ +- [BackupRestore](#backuprestore) + +_Example:_ + +```yaml +clusterName: string +backupRef: + name: string +targetPool: string +targetNode: string +pvcTemplate: + metadata: + name: string + spec: PersistentVolumeClaimSpec +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `backupRef` _[BackupRef](#backupref)_ | BackupRef references the StorageBackup resource to restore from. | | | +| `targetPool` _string_ | TargetPool overrides the pool to restore into.
Defaults to the source backup's pool. | | Optional: \{\}
| +| `targetNode` _string_ | TargetNode is the UUID of the storage node to restore onto.
Defaults to the node that originally held the backup. | | Optional: \{\}
| +| `pvcTemplate` _[PVCTemplate](#pvctemplate)_ | PVCTemplate describes the PVC to create once the restore completes. | | | + + +#### BackupRestoreStatus + + + +BackupRestoreStatus defines the observed state of BackupRestore. + + + +_Appears in:_ +- [BackupRestore](#backuprestore) + +_Example:_ + +```yaml +phase: string +message: string +clusterUUID: string +backupID: string +sourceLvolID: string +fsType: string +poolName: string +poolUUID: string +restoredLvolID: string +pvName: string +pvcName: string +pvcNamespace: string +sourceClusterUUID: string +sourceSwitchedAt: Time +startedAt: Time +completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is the high-level lifecycle shown in kubectl output. | | | +| `message` _string_ | Message contains the latest reconciliation detail or error. | | | +| `clusterUUID` _string_ | ClusterUUID is the backend cluster UUID. | | | +| `backupID` _string_ | BackupID is the backend backup UUID being restored. | | | +| `sourceLvolID` _string_ | SourceLvolID is the original logical volume UUID that was backed up. | | | +| `fsType` _string_ | FSType is the filesystem type of the original source volume, copied from
the referenced StorageBackup's status.fsType. Set on the restored
PersistentVolume so it mounts with the same filesystem it was backed up
with, instead of the CSI driver's default. | | | +| `poolName` _string_ | PoolName is the pool the restore was issued against. | | | +| `poolUUID` _string_ | PoolUUID is the backend pool UUID. | | | +| `restoredLvolID` _string_ | RestoredLvolID is the UUID of the newly-created logical volume. | | | +| `pvName` _string_ | PVName is the name of the PersistentVolume created by the controller. | | | +| `pvcName` _string_ | PVCName is the name of the PersistentVolumeClaim created from pvcTemplate. | | | +| `pvcNamespace` _string_ | PVCNamespace is the namespace of the created PVC. | | | +| `sourceClusterUUID` _string_ | SourceClusterUUID is the UUID of the cluster that originally created the backup.
Copied from the referenced StorageBackup's status.sourceClusterUUID.
When non-empty, the controller performs source-switch before and after the restore. | | | +| `sourceSwitchedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | SourceSwitchedAt records when the target cluster was switched to read from the
source cluster's S3 bucket. Cleared once source-switch local completes. | | | +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | StartedAt is when the backend restore task was accepted. | | | +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CompletedAt is when the PVC became bound. | | | + + +#### BackupSpec + + + + + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +localEndpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' +snapshotBackups: boolean +withCompression: boolean +secondaryTarget: integer +localTesting: boolean +credentialsSecretRef: + name: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `localEndpoint` _string_ | | | Pattern: `^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$`
| +| `snapshotBackups` _boolean_ | | | Optional: \{\}
| +| `withCompression` _boolean_ | | | Optional: \{\}
| +| `secondaryTarget` _integer_ | | | Optional: \{\}
| +| `localTesting` _boolean_ | | | Optional: \{\}
| +| `credentialsSecretRef` _[BackupCredentialsSecretRef](#backupcredentialssecretref)_ | CredentialsSecretRef points to the Secret holding access_key_id and secret_access_key. | | | + + +#### CapacityThresholdSpec + + + + + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +capacity: integer +provisionedCapacity: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `capacity` _integer_ | Capacity defines the absolute capacity threshold value. | | | +| `provisionedCapacity` _integer_ | ProvisionedCapacity defines the provisioned-capacity threshold value. | | | + + +#### ControlPlane + + + +ControlPlane is a singleton resource (one per namespace, named "simplyblock") +that reflects the readiness of the simplyblock control plane. It is created +automatically by the Helm chart and should not be created or deleted manually. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: ControlPlane +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})?)$' +status: + phase: string + message: string + lastChecked: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `ControlPlane` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[ControlPlaneSpec](#controlplanespec)_ | | | Optional: \{\}
| +| `status` _[ControlPlaneStatus](#controlplanestatus)_ | | | Optional: \{\}
| + + +#### ControlPlaneSpec + + + +ControlPlaneSpec holds configuration for the singleton ControlPlane resource +created by the Helm chart. + + + +_Appears in:_ +- [ControlPlane](#controlplane) + +_Example:_ + +```yaml +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 container image used for all simplyblock control-plane and
storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2).
StorageNodeSet CRs that omit spec.clusterImage inherit this value.
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\})?)$`
Optional: \{\}
| + + +#### ControlPlaneStatus + + + +ControlPlaneStatus reflects the observed readiness of the simplyblock +control plane (FDB + management API). + + + +_Appears in:_ +- [ControlPlane](#controlplane) + +_Example:_ + +```yaml +phase: string +message: string +lastChecked: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is Initializing while the control plane is not yet healthy,
and Ready once the FDB health check passes. | | Enum: [Initializing Ready]
| +| `message` _string_ | Message contains a human-readable explanation of the current phase,
for example the FDB error returned by the health endpoint. | | | +| `lastChecked` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | LastChecked is the timestamp of the most recent FDB health probe. | | | + + +#### DataRealignmentSettings + + + +DataRealignmentSettings controls the periodic, post-migration control-plane data +realignment. After one or more volumes have been moved the operator asks the +control plane to re-align its internal data structures to the new placement, +restoring fault-tolerance (FTT) and node-affinity guarantees. + + + +_Appears in:_ +- [VolumeMigrationSettings](#volumemigrationsettings) + +_Example:_ + +```yaml +enabled: boolean +interval: Duration +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enabled` _boolean_ | Enabled activates automatic post-migration data realignment for this cluster.
Defaults to true. | | Optional: \{\}
| +| `interval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#duration-v1-meta)_ | Interval is how often the operator checks whether a realignment is pending
(i.e. at least one volume has moved since the last successful realignment) and,
if so, triggers it. Explicit triggers (see the
simplyblock.io/trigger-realignment annotation) bypass this spacing. Defaults to
10m. | | Optional: \{\}
| + + +#### DrainOpsSpec + + + +DrainOpsSpec configures the drain workflow for action=remove. + + + +_Appears in:_ +- [StorageNodeOpsSpec](#storagenodeopsspec) + +_Example:_ + +```yaml +systemVolumeFilterRegex: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `systemVolumeFilterRegex` _string_ | SystemVolumeFilterRegex is a Go regular expression matched against backend
volume names. Matching volumes are treated as system volumes: excluded from
drain migration and deleted inline during the Verifying phase.
Defaults to "^sb-fio-baseline-.*". | | Optional: \{\}
| + + +#### HashicorpVaultSettings + + + +HashicorpVaultSettings configures the HashiCorp Vault endpoint the cluster uses to store keys. + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +baseURL: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `baseURL` _string_ | BaseURL is the HashiCorp Vault endpoint (e.g. https://vault.example.com:8200). | | Pattern: `^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$`
| + + +#### JournalManagerSpec + + + +JournalManagerSpec defines journal manager tuning parameters. + + + +_Appears in:_ +- [StorageNodeOverrides](#storagenodeoverrides) +- [StorageNodeSetSpec](#storagenodesetspec) + +_Example:_ + +```yaml +count: integer +percentPerDevice: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `count` _integer_ | Count is the number of journal managers to configure. | | | +| `percentPerDevice` _integer_ | PercentPerDevice is the journal manager capacity percentage per device. | | | + + +#### MetricsBackend + +_Underlying type:_ _string_ + +MetricsBackend selects the NodeMetricsProvider implementation. + +_Validation:_ +- Enum: [controlplane prometheus uniform] + +_Appears in:_ +- [VolumeAutoPlacementSettings](#volumeautoplacementsettings) + +| Field | Description | +| --- | --- | +| `controlplane` | | +| `prometheus` | | +| `uniform` | MetricsBackendUniform returns IOPS=1 for every node, disabling
IOPS-based scoring while keeping capacity/volume-count balancing active.
| + + +#### MigrationConnection + + + +MigrationConnection holds the NVMe-oF connection parameters for one path +on the migration target node, as returned by the storage API's CreateMigration. +All fields are passed verbatim to `nvme connect` in the validation Job. + + + +_Appears in:_ +- [VolumeMigrationStatus](#volumemigrationstatus) + +_Example:_ + +```yaml +nqn: string +ip: string +port: integer +transport: string +nrIoQueues: integer +reconnectDelay: integer +ctrlLossTmo: integer +fastIOFailTmo: integer +keepAliveTmo: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `nqn` _string_ | | | | +| `ip` _string_ | | | | +| `port` _integer_ | | | | +| `transport` _string_ | | | | +| `nrIoQueues` _integer_ | | | | +| `reconnectDelay` _integer_ | | | | +| `ctrlLossTmo` _integer_ | | | | +| `fastIOFailTmo` _integer_ | | | | +| `keepAliveTmo` _integer_ | | | | + + +#### NodeDrainState + + + +NodeDrainState tracks the upgrade-drain coordination state for a single worker node. + + + +_Appears in:_ +- [StorageNodeSetStatus](#storagenodesetstatus) + +_Example:_ + +```yaml +hostname: string +phase: string +startedAt: Time +message: string +activeNodeUUID: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `hostname` _string_ | Hostname is the Kubernetes node name. | | | +| `phase` _string_ | Phase is the current drain coordination phase. | | Enum: [detected shutdown_called draining restart_called complete failed]
| +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | StartedAt is when drain coordination began for this node. | | | +| `message` _string_ | Message provides additional status detail or error information. | | | +| `activeNodeUUID` _string_ | ActiveNodeUUID is the backend UUID of the storage node currently being shut
down or restarted. Used to sequence through multiple NUMA-socket nodes on
the same worker one at a time during drain coordination. | | | + + +#### NodeLatencyMetrics + + + +NodeLatencyMetrics holds fio-measured 4K NVMe-oF latency for a single backend storage node. +The benchmark volume NQN and connection details are derived at runtime from the node UUID +and the cluster NQN — they are not stored here. + + + +_Appears in:_ +- [StorageNodeSetStatus](#storagenodesetstatus) +- [StorageNodeStatus](#storagenodestatus) + +_Example:_ + +```yaml +nodeUUID: string +baselineP50NS: integer +baselineP99NS: integer +baselineMeasuredAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `nodeUUID` _string_ | NodeUUID is the backend storage node UUID. | | | +| `baselineP50NS` _integer_ | BaselineP50NS is the p50 write latency (nanoseconds) from the initial empty-cluster benchmark. | | | +| `baselineP99NS` _integer_ | BaselineP99NS is the p99 write latency (nanoseconds) from the initial empty-cluster benchmark. | | | +| `baselineMeasuredAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | BaselineMeasuredAt is when the baseline was established. | | | + + +#### NodeLoadMetrics + + + +NodeLoadMetrics holds the latency deviation state for a single storage node. + + + +_Appears in:_ +- [RebalancingMetrics](#rebalancingmetrics) + +_Example:_ + +```yaml +nodeUUID: string +latencyDeviationPct: float +volumeCount: integer +lastUpdated: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `nodeUUID` _string_ | | | | +| `latencyDeviationPct` _float_ | | | | +| `volumeCount` _integer_ | | | | +| `lastUpdated` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | | | | + + +#### NodeRecycleSpec + + + +NodeRecycleSpec configures the node-recycle action behaviour. + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +refreshSNodeAPI: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `refreshSNodeAPI` _boolean_ | RefreshSNodeAPI restarts the storage-node DaemonSet pod on each node
after the backend node is shut down and before it is restarted, ensuring
the latest image is running before the node comes back online. | | | + + +#### NodeRecycleStatus + + + +NodeRecycleStatus tracks in-progress state for the node-recycle action. +All fields are persisted in CR status so the reconciler can resume after a requeue. + + + +_Appears in:_ +- [StorageClusterStatus](#storageclusterstatus) + +_Example:_ + +```yaml +pendingNodes: + - string +processedNodes: + - string +nodePhase: string +phaseTriggered: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `pendingNodes` _string array_ | PendingNodes is the ordered list of node UUIDs still to be recycled. | | | +| `processedNodes` _string array_ | ProcessedNodes is the list of node UUIDs already recycled. | | | +| `nodePhase` _string_ | NodePhase is the current step for the node being recycled:
"snode-refresh" \| "snode-refresh-wait" \| "shutting-down" \| "restarting" \| "rebalancing" | | | +| `phaseTriggered` _boolean_ | PhaseTriggered indicates the API call for the current NodePhase was already sent. | | | + + +#### NodeStatus + + + + + + + +_Appears in:_ +- [StorageNodeSetStatus](#storagenodesetstatus) + +_Example:_ + +```yaml +uuid: string +health: boolean +status: string +cpu: integer +memory: string +volumes: integer +rpcPort: integer +lvolPort: integer +nvmfPort: integer +devices: string +uptime: string +hostname: string +mgmtIp: string +postedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `uuid` _string_ | UUID is the backend node UUID. | | | +| `health` _boolean_ | Health indicates whether health checks are currently passing. | | | +| `status` _string_ | Status is the backend lifecycle state for the node. | | | +| `cpu` _integer_ | CPU is the reported CPU allocation/count for the node. | | | +| `memory` _string_ | Memory is the reported memory value. | | | +| `volumes` _integer_ | Volumes is the current logical volume count. | | | +| `rpcPort` _integer_ | RpcPort is the node RPC service port. | | | +| `lvolPort` _integer_ | LvolPort is the logical-volume subsystem port. | | | +| `nvmfPort` _integer_ | NvmfPort is the NVMf service port. | | | +| `devices` _string_ | Devices is the backend summary of devices on this node. | | | +| `uptime` _string_ | Uptime is the reported node uptime value. | | | +| `hostname` _string_ | Hostname is the Kubernetes node hostname. | | | +| `mgmtIp` _string_ | MgmtIp is the management IP address for the node. | | | +| `postedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | PostedAt is when the storage-node add request was sent. Used to detect
timeout without blocking the reconcile goroutine. | | | + + +#### PVCTemplate + + + +PVCTemplate describes the PVC the controller will create once the restore completes. + + + +_Appears in:_ +- [BackupRestoreSpec](#backuprestorespec) + +_Example:_ + +```yaml +metadata: + name: string +spec: PersistentVolumeClaimSpec +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `metadata` _[PVCTemplateMetadata](#pvctemplatemetadata)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[PersistentVolumeClaimSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#persistentvolumeclaimspec-v1-core)_ | Spec follows core PersistentVolumeClaimSpec.
spec.resources.requests.storage must be >= the backup size. | | | + + +#### PVCTemplateMetadata + + + +PVCTemplateMetadata describes the PVC metadata fields the controller honors. + + + +_Appears in:_ +- [PVCTemplate](#pvctemplate) + +_Example:_ + +```yaml +name: string +labels: + string: string +annotations: + string: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | | | Optional: \{\}
| +| `labels` _object (keys:string, values:string)_ | | | Optional: \{\}
| +| `annotations` _object (keys:string, values:string)_ | | | Optional: \{\}
| + + +#### PersistentVolumeClaimRef + + + + + + + +_Appears in:_ +- [StorageBackupSpec](#storagebackupspec) + +_Example:_ + +```yaml +name: string +namespace: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name is the PVC name. | | | +| `namespace` _string_ | Namespace overrides the backup resource namespace for the PVC lookup. | | | + + +#### Pool + + + +Pool is the Schema for the pools API + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: Pool +metadata: + name: string +spec: + clusterName: string + status: string + capacityLimit: string + logicalVolumeMaxSize: string + dhchap: boolean + allowedNodes: + - string + qos: + iops: integer + throughput: + read: integer + readWrite: integer + write: integer + action: string + storageClassParameters: + qosRwIops: string + qosRwMbytes: string + qosRMbytes: string + qosWMbytes: string + compression: string + encryption: boolean + replicate: boolean + numDataChunks: string + numParityChunks: string + lvolPriorityClass: string + fabric: string + maxNamespacePerSubsys: string + tune2fsReservedBlocks: string +status: + uuid: string + status: string + qos: + host: string + iops: integer + throughput: + read: integer + readWrite: integer + write: integer + allowedNodes: + - string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `Pool` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[PoolSpec](#poolspec)_ | spec defines the desired state of Pool | | Required: \{\}
| +| `status` _[PoolStatus](#poolstatus)_ | status defines the observed state of Pool | | Optional: \{\}
| + + +#### PoolQoSSpec + + + +PoolQoSSpec defines pool QosSpec limits. + + + +_Appears in:_ +- [PoolSpec](#poolspec) + +_Example:_ + +```yaml +iops: integer +throughput: + read: integer + readWrite: integer + write: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `iops` _integer_ | IOPS is the IOPS limit for the pool. | | | +| `throughput` _[PoolQoSThroughputSpec](#poolqosthroughputspec)_ | Throughput contains throughput limits for the pool. | | | + + +#### PoolQoSStatus + + + +PoolQoSStatus defines observed pool QosSpec values. + + + +_Appears in:_ +- [PoolStatus](#poolstatus) + +_Example:_ + +```yaml +host: string +iops: integer +throughput: + read: integer + readWrite: integer + write: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `host` _string_ | Host is the backend host handling pool QosSpec enforcement. | | | +| `iops` _integer_ | IOPS is the observed/configured IOPS value. | | | +| `throughput` _[PoolQoSThroughputStatus](#poolqosthroughputstatus)_ | Throughput contains observed/configured throughput values. | | | + + +#### PoolQoSThroughputSpec + + + +PoolQoSThroughputSpec defines throughput QosSpec limits in MiB/s. + + + +_Appears in:_ +- [PoolQoSSpec](#poolqosspec) + +_Example:_ + +```yaml +read: integer +readWrite: integer +write: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `read` _integer_ | Read is the read throughput limit for the pool. | | | +| `readWrite` _integer_ | ReadWrite is the combined read/write throughput limit for the pool. | | | +| `write` _integer_ | Write is the write throughput limit for the pool. | | | + + +#### PoolQoSThroughputStatus + + + +PoolQoSThroughputStatus defines observed throughput QosSpec values in MiB/s. + + + +_Appears in:_ +- [PoolQoSStatus](#poolqosstatus) + +_Example:_ + +```yaml +read: integer +readWrite: integer +write: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `read` _integer_ | Read is the observed/configured read throughput value. | | | +| `readWrite` _integer_ | ReadWrite is the observed/configured combined read/write throughput value. | | | +| `write` _integer_ | Write is the observed/configured write throughput value. | | | + + +#### PoolSpec + + + +PoolSpec defines the desired state of Pool + + + +_Appears in:_ +- [Pool](#pool) + +_Example:_ + +```yaml +clusterName: string +status: string +capacityLimit: string +logicalVolumeMaxSize: string +dhchap: boolean +allowedNodes: + - string +qos: + iops: integer + throughput: + read: integer + readWrite: integer + write: integer +action: string +storageClassParameters: + qosRwIops: string + qosRwMbytes: string + qosRMbytes: string + qosWMbytes: string + compression: string + encryption: boolean + replicate: boolean + numDataChunks: string + numParityChunks: string + lvolPriorityClass: string + fabric: string + maxNamespacePerSubsys: string + tune2fsReservedBlocks: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `status` _string_ | Status is an optional desired-status hint for backend workflows.
FIXME: Unused for now | | | +| `capacityLimit` _string_ | CapacityLimit is the maximum aggregate capacity that can be allocated from this pool.
This maps to sbctl pool add --pool-max. Use sizes like 20M, 20G, or 0 for unlimited. | | | +| `logicalVolumeMaxSize` _string_ | LogicalVolumeMaxSize is the maximum size allowed for any single logical volume
created in this pool. This maps to sbctl pool add --lvol-max. Use sizes like
20M, 20G, or 0 for unlimited. | | | +| `dhchap` _boolean_ | DHCHAP enables DH-HMAC-CHAP key generation for the pool. Authentication is only
enforced when allowedNodes is non-empty | false | | +| `allowedNodes` _string array_ | AllowedNodes is the list of Kubernetes worker node names allowed to access volumes
in this pool. The operator resolves each node name to a deterministic NQN derived
from the node's UID: nqn.2014-08.io.simplyblock:uuid:.
The CSI node uses the same formula so no manual NQN management is required. | | | +| `qos` _[PoolQoSSpec](#poolqosspec)_ | QosSpec defines QosSpec limits for the pool. | | | +| `action` _string_ | Action triggers an imperative pool operation.
FIXME: Unused for now | | | +| `storageClassParameters` _[StorageClassParameters](#storageclassparameters)_ | StorageClassParameters sets default StorageClass parameter values for volumes in this pool. | \{ \} | | + + +#### PoolStatus + + + +PoolStatus defines the observed state of Pool. + + + +_Appears in:_ +- [Pool](#pool) + +_Example:_ + +```yaml +uuid: string +status: string +qos: + host: string + iops: integer + throughput: + read: integer + readWrite: integer + write: integer +allowedNodes: + - string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `uuid` _string_ | UUID is the backend pool UUID. | | | +| `status` _string_ | Status is the backend lifecycle status. | | | +| `qos` _[PoolQoSStatus](#poolqosstatus)_ | QoS contains observed/configured QoS values. | | | +| `allowedNodes` _string array_ | AllowedNodes lists the Kubernetes node names currently registered on the backend. | | | + + +#### RebalancingMetrics + + + +RebalancingMetrics is written by the VolumeRebalancerReconciler each evaluation cycle. + + + +_Appears in:_ +- [StorageClusterStatus](#storageclusterstatus) + +_Example:_ + +```yaml +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 all nodes. | | | +| `maxDeviationPct` _float_ | MaxDeviationPct is the highest per-node latency deviation (used as ImbalancePercent). | | | +| `hottestNodeUUID` _string_ | | | | +| `coolestNodeUUID` _string_ | | | | +| `imbalancePercent` _float_ | | | | +| `lastEvaluatedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | | | | +| `lastMigrationAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | | | | +| `nodeMetrics` _[NodeLoadMetrics](#nodeloadmetrics) array_ | | | | + + +#### ReplicationError + + + +ReplicationError stores timestamped error messages + + + +_Appears in:_ +- [VolumeReplicationStatus](#volumereplicationstatus) + +_Example:_ + +```yaml +timestamp: Time +message: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `timestamp` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | | | | +| `message` _string_ | | | | + + +#### SnapshotReplication + + + +SnapshotReplication is the Schema for the snapshotreplications API + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: SnapshotReplication +metadata: + name: string +spec: + sourceCluster: string + targetCluster: string + targetPool: string + sourcePool: string + timeout: integer + interval: integer + action: string + includeVolumeIDs: + - string + excludeVolumeIDs: + - string + volumeIDs: + - string +status: + configured: boolean + observedFailbackGeneration: integer + volumes: + - volumeID: string + phase: string + lastSnapshotID: string + lastReplicationTime: Time + replicatedCount: integer + errors: + - timestamp: Time + message: string + conditions: + - Condition +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `SnapshotReplication` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[SnapshotReplicationSpec](#snapshotreplicationspec)_ | spec defines the desired state of SnapshotReplication | | Required: \{\}
| +| `status` _[SnapshotReplicationStatus](#snapshotreplicationstatus)_ | status defines the observed state of SnapshotReplication | | Optional: \{\}
| + + +#### SnapshotReplicationSpec + + + +SnapshotReplicationSpec defines the desired state of SnapshotReplication + + + +_Appears in:_ +- [SnapshotReplication](#snapshotreplication) + +_Example:_ + +```yaml +sourceCluster: string +targetCluster: string +targetPool: string +sourcePool: string +timeout: integer +interval: integer +action: string +includeVolumeIDs: + - string +excludeVolumeIDs: + - string +volumeIDs: + - string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `sourceCluster` _string_ | Source cluster for the snapshots | | | +| `targetCluster` _string_ | Target cluster for replication | | | +| `targetPool` _string_ | Target cluster pool for replication | | | +| `sourcePool` _string_ | required for failback to a fresh source cluster | | | +| `timeout` _integer_ | snapshot replication timeout | | | +| `interval` _integer_ | snapshot replication interval in seconds (default: 300sec) | | | +| `action` _string_ | | | Enum: [failback]
| +| `includeVolumeIDs` _string array_ | Optional: only these volumes are included in failback.
If empty, all volumes are candidates unless excluded below. | | | +| `excludeVolumeIDs` _string array_ | Optional: volumes to exclude from failback. | | | +| `volumeIDs` _string array_ | Optional: list of volumes to replicate. Empty means all volumes | | | + + +#### SnapshotReplicationStatus + + + +SnapshotReplicationStatus defines the observed state of SnapshotReplication. + + + +_Appears in:_ +- [SnapshotReplication](#snapshotreplication) + +_Example:_ + +```yaml +configured: boolean +observedFailbackGeneration: integer +volumes: + - volumeID: string + phase: string + lastSnapshotID: string + lastReplicationTime: Time + replicatedCount: integer + errors: + - timestamp: Time + message: string +conditions: + - Condition +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `configured` _boolean_ | | | | +| `observedFailbackGeneration` _integer_ | The metadata.generation value for which failback was last processed. | | | +| `volumes` _[VolumeReplicationStatus](#volumereplicationstatus) array_ | Per-volume replication status | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#condition-v1-meta) array_ | Conditions provides human-readable status conditions for kubectl get output. | | | + + +#### StorageBackup + + + +StorageBackup is the Schema for the storagebackups API. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageBackup +metadata: + name: string +spec: + clusterName: string + pvcRef: + name: string + namespace: string + snapshotName: string + sourceClusterUUID: string +status: + phase: string + apiStatus: string + message: string + clusterUUID: string + pvcNamespace: string + pvName: string + poolName: string + poolUUID: string + lvolID: string + lvolName: string + fsType: string + snapshotID: string + snapshotName: string + sourceClusterUUID: string + backupID: string + s3ID: integer + nodeID: string + prevBackupID: string + size: integer + allowedHosts: + - 'map[string]string' + createdAt: Time + completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `StorageBackup` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[StorageBackupSpec](#storagebackupspec)_ | spec defines the desired state of StorageBackup | | Required: \{\}
| +| `status` _[StorageBackupStatus](#storagebackupstatus)_ | status defines the observed state of StorageBackup | | Optional: \{\}
| + + +#### StorageBackupSpec + + + +StorageBackupSpec defines the desired state of StorageBackup. + + + +_Appears in:_ +- [StorageBackup](#storagebackup) + +_Example:_ + +```yaml +clusterName: string +pvcRef: + name: string + namespace: string +snapshotName: string +sourceClusterUUID: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `pvcRef` _[PersistentVolumeClaimRef](#persistentvolumeclaimref)_ | PVCRef identifies the PVC whose backing Simplyblock volume should be snapshotted and backed up.
Not required when SourceClusterUUID is set (imported backup). | | Optional: \{\}
| +| `snapshotName` _string_ | SnapshotName optionally overrides the internally-created snapshot name. | | Optional: \{\}
| +| `sourceClusterUUID` _string_ | SourceClusterUUID, when non-empty, marks this StorageBackup as imported from another cluster.
The StorageBackup controller will not create snapshots or backups for imported resources.
Set by the BackupImport controller; do not set manually. | | Optional: \{\}
| + + +#### StorageBackupStatus + + + +StorageBackupStatus defines the observed state of StorageBackup. + + + +_Appears in:_ +- [StorageBackup](#storagebackup) + +_Example:_ + +```yaml +phase: string +apiStatus: string +message: string +clusterUUID: string +pvcNamespace: string +pvName: string +poolName: string +poolUUID: string +lvolID: string +lvolName: string +fsType: string +snapshotID: string +snapshotName: string +sourceClusterUUID: string +backupID: string +s3ID: integer +nodeID: string +prevBackupID: string +size: integer +allowedHosts: + - 'map[string]string' +createdAt: Time +completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _string_ | Phase is the high-level backup lifecycle shown in kubectl output. | | | +| `apiStatus` _string_ | APIStatus is the raw status returned by the backup API. | | | +| `message` _string_ | Message contains the latest reconciliation detail or error. | | | +| `clusterUUID` _string_ | ClusterUUID is the backend cluster UUID. | | | +| `pvcNamespace` _string_ | PVCNamespace is the resolved PVC namespace. | | | +| `pvName` _string_ | PVName is the bound PV name. | | | +| `poolName` _string_ | PoolName is the Simplyblock pool name derived from the CSI volume handle. | | | +| `poolUUID` _string_ | PoolUUID is the backend pool UUID. | | | +| `lvolID` _string_ | LvolID is the Simplyblock volume UUID. | | | +| `lvolName` _string_ | LvolName is the backend logical volume name. | | | +| `fsType` _string_ | FSType is the filesystem type of the source PersistentVolume (e.g. "ext4",
"xfs"), captured at backup time so a restore can preserve it regardless of
which StorageClass the restored PVC ends up using. | | | +| `snapshotID` _string_ | SnapshotID is the internally-created snapshot UUID used for the backup request. | | | +| `snapshotName` _string_ | SnapshotName is the snapshot name used for the backup request. | | | +| `sourceClusterUUID` _string_ | SourceClusterUUID is set for imported backups; identifies the cluster that originally
created the backup. When non-empty and different from the restore target cluster UUID,
BackupRestore will automatically perform source-switch operations around the restore. | | | +| `backupID` _string_ | BackupID is the backend backup UUID. | | | +| `s3ID` _integer_ | S3ID is the backend S3 object identifier. | | | +| `nodeID` _string_ | NodeID is the source storage node UUID. | | | +| `prevBackupID` _string_ | PrevBackupID links the previous backup in the chain. | | | +| `size` _integer_ | Size is the backup size in bytes. | | | +| `allowedHosts` _object array_ | AllowedHosts contains the allowed host metadata returned by the backup API. | | | +| `createdAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CreatedAt is when the backup was created. | | | +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CompletedAt is when the backup completed. | | | + + +#### StorageClassParameters + + + +StorageClassParameters defines the default StorageClass parameter values for volumes in this pool. +These are passed as-is to the CSI driver when the StorageClass is created. +cluster_id and pool_name are always set automatically and cannot be overridden here. + + + +_Appears in:_ +- [PoolSpec](#poolspec) + +_Example:_ + +```yaml +qosRwIops: string +qosRwMbytes: string +qosRMbytes: string +qosWMbytes: string +compression: string +encryption: boolean +replicate: boolean +numDataChunks: string +numParityChunks: string +lvolPriorityClass: string +fabric: string +maxNamespacePerSubsys: string +tune2fsReservedBlocks: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `qosRwIops` _string_ | QosRwIops sets the read/write IOPS limit (0 = unlimited). | 0 | | +| `qosRwMbytes` _string_ | QosRwMbytes sets the read/write throughput limit in MB/s (0 = unlimited). | 0 | | +| `qosRMbytes` _string_ | QosRMbytes sets the read throughput limit in MB/s (0 = unlimited). | 0 | | +| `qosWMbytes` _string_ | QosWMbytes sets the write throughput limit in MB/s (0 = unlimited). | 0 | | +| `compression` _string_ | Compression enables compression for logical volumes. | False | | +| `encryption` _boolean_ | Encryption enables encryption for logical volumes. | false | | +| `replicate` _boolean_ | Replicate enables replication for logical volumes. | false | | +| `numDataChunks` _string_ | NumDataChunks is the number of data chunks (distr_ndcs). | 1 | | +| `numParityChunks` _string_ | NumParityChunks is the number of parity chunks (distr_npcs). | 1 | | +| `lvolPriorityClass` _string_ | LvolPriorityClass sets the logical volume priority class. | 0 | | +| `fabric` _string_ | Fabric is the transport fabric (e.g. tcp). | tcp | | +| `maxNamespacePerSubsys` _string_ | MaxNamespacePerSubsys limits namespaces per NVMf subsystem. | 1 | | +| `tune2fsReservedBlocks` _string_ | Tune2fsReservedBlocks sets the ext4 reserved-blocks percentage. | 0 | | + + +#### StorageCluster + + + +StorageCluster is the Schema for the storageclusters API + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageCluster +metadata: + name: string +spec: + enableNodeAffinity: boolean + stripe: + dataChunks: integer + parityChunks: integer + haType: string + action: string + nodeRecycle: + refreshSNodeAPI: boolean + isSingleNode: boolean + strictNodeAntiAffinity: boolean + qpairCount: integer + blockSize: integer + pageSizeInBlocks: integer + maxQueueSize: integer + inflightIOThreshold: integer + fabricType: string + clientDataIfname: string + maxFaultTolerance: integer + nvmfBasePort: integer + rpcBasePort: integer + snodeApiPort: integer + warningThreshold: + capacity: integer + provisionedCapacity: integer + criticalThreshold: + capacity: integer + provisionedCapacity: integer + clientQpairCount: integer + backup: + localEndpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' + snapshotBackups: boolean + withCompression: boolean + secondaryTarget: integer + localTesting: boolean + credentialsSecretRef: + name: string + hashicorpVaultSettings: + baseURL: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' + volumeMigrationSettings: + enabled: boolean + rebalancerImage: string + dataRealignment: + enabled: boolean + interval: Duration + volumeAutoPlacement: + enabled: boolean + migrationEnabled: boolean + evaluationInterval: Duration + imbalanceThreshold: integer + minHotColdDifferencePct: integer + defaultCoolDownSeconds: integer + maxVolumeMigrationsPerCycle: integer + storageNodeCandidateCount: integer + metricsBackend: MetricsBackend + prometheusURL: string + latencyBenchmarkEnabled: boolean + latencyBenchmarkInterval: Duration + iopsWeight: float + throughputWeight: float + enableFailureDomains: boolean +status: + uuid: string + phase: string + subPhase: string + clusterName: string + mgmtNodes: integer + storageNodes: integer + nqn: string + status: string + rebalancing: boolean + pendingDataRealignment: boolean + lastDataRealignmentAt: Time + erasureCodingScheme: string + lastUpdated: Time + created: Time + configured: boolean + maxFaultTolerance: integer + actionStatus: + action: string + nodeUUID: string + state: string + message: string + updatedAt: Time + observedGeneration: integer + triggered: boolean + subPhase: string + volumesMigrated: integer + volumesPending: integer + nodeRecycleStatus: + pendingNodes: + - string + processedNodes: + - string + nodePhase: string + phaseTriggered: boolean + rebalancingMetrics: + 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 | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `StorageCluster` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[StorageClusterSpec](#storageclusterspec)_ | spec defines the desired state of StorageCluster | | Required: \{\}
| +| `status` _[StorageClusterStatus](#storageclusterstatus)_ | status defines the observed state of StorageCluster | | Optional: \{\}
| + + +#### StorageClusterSpec + + + +StorageClusterSpec defines the desired state of StorageCluster + + + +_Appears in:_ +- [StorageCluster](#storagecluster) + +_Example:_ + +```yaml +enableNodeAffinity: boolean +stripe: + dataChunks: integer + parityChunks: integer +haType: string +action: string +nodeRecycle: + refreshSNodeAPI: boolean +isSingleNode: boolean +strictNodeAntiAffinity: boolean +qpairCount: integer +blockSize: integer +pageSizeInBlocks: integer +maxQueueSize: integer +inflightIOThreshold: integer +fabricType: string +clientDataIfname: string +maxFaultTolerance: integer +nvmfBasePort: integer +rpcBasePort: integer +snodeApiPort: integer +warningThreshold: + capacity: integer + provisionedCapacity: integer +criticalThreshold: + capacity: integer + provisionedCapacity: integer +clientQpairCount: integer +backup: + localEndpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' + snapshotBackups: boolean + withCompression: boolean + secondaryTarget: integer + localTesting: boolean + credentialsSecretRef: + name: string +hashicorpVaultSettings: + baseURL: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$' +volumeMigrationSettings: + enabled: boolean + rebalancerImage: string + dataRealignment: + enabled: boolean + interval: Duration +volumeAutoPlacement: + enabled: boolean + migrationEnabled: boolean + evaluationInterval: Duration + imbalanceThreshold: integer + minHotColdDifferencePct: integer + defaultCoolDownSeconds: integer + maxVolumeMigrationsPerCycle: integer + storageNodeCandidateCount: integer + metricsBackend: MetricsBackend + prometheusURL: string + latencyBenchmarkEnabled: boolean + latencyBenchmarkInterval: Duration + iopsWeight: float + throughputWeight: float +enableFailureDomains: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enableNodeAffinity` _boolean_ | EnableNodeAffinity enables node-affinity placement for storage components. | | | +| `stripe` _[StripeSpec](#stripespec)_ | StripeSpec configures erasure-coding data/parity chunk counts. | | | +| `haType` _string_ | HAType defines the backend high-availability mode. | | | +| `action` _string_ | Action triggers a cluster-level action. | | Enum: [activate expand shutdown start restart node-recycle]
| +| `nodeRecycle` _[NodeRecycleSpec](#noderecyclespec)_ | NodeRecycle configures the node-recycle action. | | | +| `isSingleNode` _boolean_ | IsSingleNode enables single-node cluster mode. | | | +| `strictNodeAntiAffinity` _boolean_ | StrictNodeAntiAffinity enforces strict anti-affinity between storage nodes. | | | +| `qpairCount` _integer_ | QpairCount defines the NVMe queue-pair count used by the cluster. | | | +| `blockSize` _integer_ | BlockSize defines the logical block size in bytes. | | | +| `pageSizeInBlocks` _integer_ | PageSizeInBlocks defines page size expressed in blocks. | | | +| `maxQueueSize` _integer_ | MaxQueueSize defines the maximum backend queue size. | | | +| `inflightIOThreshold` _integer_ | InflightIOThreshold defines the inflight I/O threshold. | | | +| `fabricType` _string_ | FabricType defines the storage fabric type. | | | +| `clientDataIfname` _string_ | ClientDataIfname defines the client data network interface. | | | +| `maxFaultTolerance` _integer_ | MaxFaultTolerance defines the maximum tolerated concurrent faults. | | | +| `nvmfBasePort` _integer_ | NvmfBasePort defines the base NVMf service port. | | | +| `rpcBasePort` _integer_ | RpcBasePort defines the base RPC service port. | | | +| `snodeApiPort` _integer_ | SnodeApiPort defines the storage-node API port. | | | +| `warningThreshold` _[CapacityThresholdSpec](#capacitythresholdspec)_ | WarningThresholdSpec defines warning-level capacity thresholds. | | | +| `criticalThreshold` _[CapacityThresholdSpec](#capacitythresholdspec)_ | CriticalThresholdSpec defines critical-level capacity thresholds. | | | +| `clientQpairCount` _integer_ | ClientQpairCount defines client-side queue-pair count. | | | +| `backup` _[BackupSpec](#backupspec)_ | Backup specifies the specification for backup to S3 configuration | | | +| `hashicorpVaultSettings` _[HashicorpVaultSettings](#hashicorpvaultsettings)_ | HashicorpVaultSettings configures the Vault endpoint used by the cluster for key storage. | | | +| `volumeMigrationSettings` _[VolumeMigrationSettings](#volumemigrationsettings)_ | VolumeMigrationSettings controls volume migration for this cluster. | | Optional: \{\}
| +| `volumeAutoPlacement` _[VolumeAutoPlacementSettings](#volumeautoplacementsettings)_ | VolumeAutoPlacement configures automatic, latency-driven volume rebalancing. When
nil/disabled the operator performs only manually-triggered VolumeMigrations. | | Optional: \{\}
| +| `enableFailureDomains` _boolean_ | EnableFailureDomains opts the cluster into failure-domain mode. When enabled, every
storage node must declare a failure-domain group so the control plane can spread
erasure-coding chunks across independent fault groups. Immutable once set — failure-
domain mode cannot be toggled on a live cluster. | | Optional: \{\}
| + + +#### StorageClusterStatus + + + +StorageClusterStatus defines the observed state of StorageCluster. + + + +_Appears in:_ +- [StorageCluster](#storagecluster) + +_Example:_ + +```yaml +uuid: string +phase: string +subPhase: string +clusterName: string +mgmtNodes: integer +storageNodes: integer +nqn: string +status: string +rebalancing: boolean +pendingDataRealignment: boolean +lastDataRealignmentAt: Time +erasureCodingScheme: string +lastUpdated: Time +created: Time +configured: boolean +maxFaultTolerance: integer +actionStatus: + action: string + nodeUUID: string + state: string + message: string + updatedAt: Time + observedGeneration: integer + triggered: boolean + subPhase: string + volumesMigrated: integer + volumesPending: integer +nodeRecycleStatus: + pendingNodes: + - string + processedNodes: + - string + nodePhase: string + phaseTriggered: boolean +rebalancingMetrics: + 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 | +| --- | --- | --- | --- | +| `uuid` _string_ | UUID is the backend cluster UUID. | | | +| `phase` _string_ | Phase tracks the cluster creation lifecycle to prevent concurrent reconcilers
from creating duplicate clusters. Set to "creation" while a creation is in
progress and cleared once the cluster UUID is persisted. | | | +| `subPhase` _string_ | SubPhase tracks the step within the current Phase. Reserved for future
sub-state machine expansion; currently only "creating" is used. | | | +| `clusterName` _string_ | ClusterName is the resolved backend cluster name. | | | +| `mgmtNodes` _integer_ | MgmtNodes is the number of management nodes.
FIXME: Unused for now (API update required?) | | | +| `storageNodes` _integer_ | StorageNodes is the number of storage nodes.
FIXME: Unused for now (API update required?) | | | +| `nqn` _string_ | NQN is the cluster NVM subsystem qualified name. | | | +| `status` _string_ | Status is the backend-reported lifecycle status. | | | +| `rebalancing` _boolean_ | Rebalancing indicates whether cluster rebalancing is currently active. | | | +| `pendingDataRealignment` _boolean_ | PendingDataRealignment indicates that at least one volume has been moved since
the last successful control-plane data realignment, so a realignment is due on
the next DataRealignment.Interval tick. It is persisted so a pending realignment
survives an operator restart, and is cleared once a realignment completes
successfully. | | Optional: \{\}
| +| `lastDataRealignmentAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | LastDataRealignmentAt is the time of the last successful control-plane data
realignment. It is used to space realignments by DataRealignment.Interval and to
avoid re-running at the end of an interval when nothing is pending. | | Optional: \{\}
| +| `erasureCodingScheme` _string_ | ErasureCodingScheme is the active erasure-coding layout, for example "2x1". | | | +| `lastUpdated` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | LastUpdated is the last backend update timestamp.
FIXME: Unused for now (API update required?) | | | +| `created` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | Created is the backend creation timestamp.
FIXME: Unused for now (API update required?) | | | +| `configured` _boolean_ | Configured indicates whether initial cluster setup completed. | | | +| `maxFaultTolerance` _integer_ | MaxFaultTolerance is the backend-reported maximum number of nodes that can
be simultaneously offline (failed, drained, or restarted) without violating
the cluster's redundancy guarantees. | | | +| `actionStatus` _[ActionStatus](#actionstatus)_ | ActionStatus tracks the most recent action execution state. | | | +| `nodeRecycleStatus` _[NodeRecycleStatus](#noderecyclestatus)_ | NodeRecycleStatus tracks in-progress state for the node-recycle action. | | | +| `rebalancingMetrics` _[RebalancingMetrics](#rebalancingmetrics)_ | RebalancingMetrics is updated by the auto-rebalancer each evaluation cycle. | | Optional: \{\}
| + + +#### StorageNode + + + +StorageNode is the Schema for a single backend storage node instance. +One StorageNode CR exists per (workerNode, socketIndex) pair and is owned +by the parent StorageNodeSet. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNode +metadata: + name: string +spec: + storageNodeSetRef: string + workerNode: string + socketId: string + nodeIndex: integer + socketIndex: integer + overrides: + maxLogicalVolumeCount: integer + maxSize: string + spdkImage: string + spdkProxyImage: string + corePercentage: integer + spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' + journalManager: + count: integer + percentPerDevice: integer + pcieAllowList: + - string + pcieDenyList: + - string + pcieModel: string + driveSizeRange: string + deviceNames: + - string + enableCpuTopology: boolean + reservedSystemCPU: string + ubuntuHost: boolean + skipKubeletConfiguration: boolean + failureDomain: integer + expand: boolean +status: + uuid: string + status: string + health: boolean + hostname: string + uptime: string + resources: + cpu: integer + memory: string + volumes: integer + devices: string + ports: + management: string + nvmeof: integer + lvol: integer + rpc: integer + postedAt: Time + activeOpsRef: string + latencyMetrics: + nodeUUID: string + baselineP50NS: integer + baselineP99NS: integer + baselineMeasuredAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `StorageNode` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[StorageNodeSpec](#storagenodespec)_ | | | | +| `status` _[StorageNodeStatus](#storagenodestatus)_ | | | | + + +#### StorageNodeOps + + + +StorageNodeOps is a one-shot operational CR targeting a single StorageNode. +Analogous to a Kubernetes Job — it drives an action (shutdown, restart, suspend, +resume, remove/drain) to completion and records the result. Only one +StorageNodeOps can be active per StorageNode at a time. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeOps +metadata: + name: string +spec: + storageNodeRef: string + action: string + targetWorkerNode: string + force: boolean + reattachVolume: boolean + newSsdPcie: + - string + drain: + systemVolumeFilterRegex: string +status: + phase: StorageNodeOpsPhase + subPhase: StorageNodeOpsSubPhase + message: string + volumesMigrated: integer + volumesPending: integer + triggered: boolean + startedAt: Time + completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `StorageNodeOps` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[StorageNodeOpsSpec](#storagenodeopsspec)_ | | | | +| `status` _[StorageNodeOpsStatus](#storagenodeopsstatus)_ | | | | + + +#### StorageNodeOpsPhase + +_Underlying type:_ _string_ + +StorageNodeOpsPhase is the lifecycle phase of a StorageNodeOps. + +_Validation:_ +- Enum: [Pending Running Succeeded Failed] + +_Appears in:_ +- [StorageNodeOpsStatus](#storagenodeopsstatus) + +| Field | Description | +| --- | --- | +| `Pending` | | +| `Running` | | +| `Succeeded` | | +| `Failed` | | + + +#### StorageNodeOpsSpec + + + +StorageNodeOpsSpec defines the desired state of a StorageNodeOps. + + + +_Appears in:_ +- [StorageNodeOps](#storagenodeops) + +_Example:_ + +```yaml +storageNodeRef: string +action: string +targetWorkerNode: string +force: boolean +reattachVolume: boolean +newSsdPcie: + - string +drain: + systemVolumeFilterRegex: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `storageNodeRef` _string_ | StorageNodeRef is the name of the target StorageNode. Immutable. | | Required: \{\}
| +| `action` _string_ | Action is the operation to perform. Immutable. | | Enum: [shutdown restart suspend resume remove migrate]
Required: \{\}
| +| `targetWorkerNode` _string_ | TargetWorkerNode is the Kubernetes worker hostname the storage node is
relocated onto. Required (and only used) when action=migrate.
A migration is NOT a drain/remove: the storage node keeps its backend UUID
and its partition / logical-volume assignments follow it. The operator
issues a control-plane restart pointed at the target host's
storage-node-api (node_address), waits for the node to come back online
there, then /promotes it (starting a rebalance) and re-points the
StorageNode's spec.workerNode and the owning StorageNodeSet.workerNodes
from the source worker to this one. No fresh storage node is provisioned
and no VolumeMigration CRs are created. Immutable. | | Optional: \{\}
| +| `force` _boolean_ | Force enables forced execution where the backend supports it. | | Optional: \{\}
| +| `reattachVolume` _boolean_ | ReattachVolume reattaches volumes during the node restart.
Applicable when action=restart or action=migrate. | | Optional: \{\}
| +| `newSsdPcie` _string array_ | NewSsdPcie lists additional NVMe PCIe addresses to bind on the target host
during a migration. Passed through to the control-plane restart as
new_ssd_pcie. Only applicable when action=migrate. | | Optional: \{\}
| +| `drain` _[DrainOpsSpec](#drainopsspec)_ | Drain configures the drain workflow. Only applicable when action=remove. | | Optional: \{\}
| + + +#### StorageNodeOpsStatus + + + +StorageNodeOpsStatus holds the observed state of a StorageNodeOps. + + + +_Appears in:_ +- [StorageNodeOps](#storagenodeops) + +_Example:_ + +```yaml +phase: StorageNodeOpsPhase +subPhase: StorageNodeOpsSubPhase +message: string +volumesMigrated: integer +volumesPending: integer +triggered: boolean +startedAt: Time +completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _[StorageNodeOpsPhase](#storagenodeopsphase)_ | Phase is the high-level lifecycle phase. | | Enum: [Pending Running Succeeded Failed]
Optional: \{\}
| +| `subPhase` _[StorageNodeOpsSubPhase](#storagenodeopssubphase)_ | SubPhase tracks the active drain step when action=remove and phase=Running. | | Enum: [Validating Suspending Migrating Verifying Removing Preparing Restarting Promoting]
Optional: \{\}
| +| `message` _string_ | Message is a human-readable description of the current state or failure reason. | | Optional: \{\}
| +| `volumesMigrated` _integer_ | VolumesMigrated is the count of volumes successfully migrated (drain only). | | Optional: \{\}
| +| `volumesPending` _integer_ | VolumesPending is the count of volumes awaiting migration (drain only). | | Optional: \{\}
| +| `triggered` _boolean_ | Triggered indicates the backend action POST has been sent (used during
Suspending to avoid duplicate POSTs across reconcile iterations). | | Optional: \{\}
| +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | StartedAt is when the operation began. | | Optional: \{\}
| +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CompletedAt is when the operation finished (successfully or not). | | Optional: \{\}
| + + +#### StorageNodeOpsSubPhase + +_Underlying type:_ _string_ + +StorageNodeOpsSubPhase is the active sub-phase during a running op: the drain +steps when action=remove, and the Preparing → Migrating → Promoting steps when +action=migrate. + +_Validation:_ +- Enum: [Validating Suspending Migrating Verifying Removing Preparing Restarting Promoting] + +_Appears in:_ +- [StorageNodeOpsStatus](#storagenodeopsstatus) + +| Field | Description | +| --- | --- | +| `Validating` | | +| `Suspending` | | +| `Migrating` | | +| `Verifying` | | +| `Removing` | | +| `Preparing` | StorageNodeOpsSubPhasePreparing marks that a migrate op is preparing the
target worker: cloning per-node config, labeling it into the storage
plane, and waiting until its storage-node-api pod is Ready and its per-pod
DNS name is published in the EndpointSlice — the precondition for the
control-plane restart to resolve node_address.
| +| `Restarting` | StorageNodeOpsSubPhaseRestarting marks that a migrate op has issued the
control-plane restart and confirmed the node entered in_restart; it is
now waiting for the node to come back online on the target host. The
restart is asynchronous, so the op only advances to Promoting after the
node has left online (restart started) and returned to online (restart
finished) — issuing /promote earlier races the in-flight restart's node
writes and leaves the relocated devices stuck in "new".
| +| `Promoting` | StorageNodeOpsSubPhasePromoting marks that a migrate op has issued the
control-plane /promote for the relocated node (guards against re-promoting).
| + + +#### StorageNodeOverrides + + + +StorageNodeOverrides holds per-node configuration that overrides the parent +StorageNodeSet fleet defaults for a specific worker node. Populated by the +StorageNodeSetReconciler from StorageNodeSet.spec.nodeConfigs[workerNode] on +every reconcile. The StorageNodeSet is the single source of truth — users +should not edit this struct directly on the StorageNode. + +Fields here mirror the configurable (non-immutable, non-infrastructure) fields +of StorageNodeSetSpec. When a field is set here it takes precedence over the +fleet default; when omitted the fleet default applies. + + + +_Appears in:_ +- [StorageNodeSetSpec](#storagenodesetspec) +- [StorageNodeSpec](#storagenodespec) + +_Example:_ + +```yaml +maxLogicalVolumeCount: integer +maxSize: string +spdkImage: string +spdkProxyImage: string +corePercentage: integer +spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' +journalManager: + count: integer + percentPerDevice: integer +pcieAllowList: + - string +pcieDenyList: + - string +pcieModel: string +driveSizeRange: string +deviceNames: + - string +enableCpuTopology: boolean +reservedSystemCPU: string +ubuntuHost: boolean +skipKubeletConfiguration: boolean +failureDomain: integer +expand: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `maxLogicalVolumeCount` _integer_ | MaxLogicalVolumeCount overrides the maximum number of logical volumes for this node. | | Optional: \{\}
| +| `maxSize` _string_ | MaxSize overrides the maximum allocatable size of huge pages for this node. | | Optional: \{\}
| +| `spdkImage` _string_ | SpdkImage overrides the SPDK image for this node (e.g. for phased rollouts). | | Optional: \{\}
| +| `spdkProxyImage` _string_ | SpdkProxyImage overrides the SPDK proxy image for this node. | | Optional: \{\}
| +| `corePercentage` _integer_ | CorePercentage overrides the percentage of cores allocated to SPDK for this node (0-99). | | Optional: \{\}
| +| `spdkSystemMemory` _string_ | SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node
(e.g. "4G", "512M"). | | Pattern: `^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$`
Optional: \{\}
| +| `journalManager` _[JournalManagerSpec](#journalmanagerspec)_ | JournalManagerSpec overrides journal manager tuning for this node. | | Optional: \{\}
| +| `pcieAllowList` _string array_ | PcieAllowList overrides the list of PCI addresses allowed for use on this node. | | Optional: \{\}
| +| `pcieDenyList` _string array_ | PcieDenyList overrides the list of PCI addresses excluded from use on this node. | | Optional: \{\}
| +| `pcieModel` _string_ | PcieModel overrides the PCI model filter for this node. | | Optional: \{\}
| +| `driveSizeRange` _string_ | DriveSizeRange overrides the drive size range filter for this node. | | Optional: \{\}
| +| `deviceNames` _string array_ | DeviceNames explicitly defines the NVMe namespace names to use on this node
(e.g. ["nvme0n1","nvme1n1"]). | | Optional: \{\}
| +| `enableCpuTopology` _boolean_ | EnableCpuTopology overrides topology-aware CPU handling for this node. | | Optional: \{\}
| +| `reservedSystemCPU` _string_ | ReservedSystemCPU overrides the CPUs reserved for system workloads on this node. | | Optional: \{\}
| +| `ubuntuHost` _boolean_ | UbuntuHost overrides the Ubuntu host OS flag for this node. | | Optional: \{\}
| +| `skipKubeletConfiguration` _boolean_ | SkipKubeletConfiguration overrides whether kubelet configuration changes are
skipped for this node. | | Optional: \{\}
| +| `failureDomain` _integer_ | FailureDomain is the failure-domain group index (≥ 1) for this node.
Required when the parent StorageCluster has enableFailureDomains=true.
Overrides StorageNodeSet.spec.nodeFailureDomains[workerNode] when both are set. | | Minimum: 1
Optional: \{\}
| +| `expand` _boolean_ | Expand marks this node as a cluster-expansion add. When true the backend
node-add endpoint receives expand=true, triggering rebalancing behaviour
appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. | | Optional: \{\}
| + + +#### StorageNodePorts + + + +StorageNodePorts groups the network port and address fields reported by the backend. + + + +_Appears in:_ +- [StorageNodeStatus](#storagenodestatus) + +_Example:_ + +```yaml +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_ | NvmeOf is the NVMe-oF fabric port. | | Optional: \{\}
| +| `lvol` _integer_ | Lvol is the logical-volume subsystem port. | | Optional: \{\}
| +| `rpc` _integer_ | Rpc is the RPC/management API port. | | Optional: \{\}
| + + +#### StorageNodeResources + + + +StorageNodeResources groups compute and storage resource fields reported by the backend. + + + +_Appears in:_ +- [StorageNodeStatus](#storagenodestatus) + +_Example:_ + +```yaml +cpu: integer +memory: string +volumes: integer +devices: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `cpu` _integer_ | CPU is the number of SPDK CPU cores allocated to this node. | | Optional: \{\}
| +| `memory` _string_ | Memory is the SPDK memory allocation reported by the backend. | | Optional: \{\}
| +| `volumes` _integer_ | Volumes is the current number of logical volumes on this node. | | Optional: \{\}
| +| `devices` _string_ | Devices is the device summary (online/total) reported by the backend. | | Optional: \{\}
| + + +#### StorageNodeSet + + + +StorageNodeSet is the Schema for the storagenodesets API + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: StorageNodeSet +metadata: + name: string +spec: + clusterName: string + clusterImage: '^($|(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})?)$' + maxLogicalVolumeCount: integer + maxSize: string + spdkImage: '^($|(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})?)$' + spdkProxyImage: '^($|(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})?)$' + mgmtIfname: string + partitions: integer + journalManager: + count: integer + percentPerDevice: integer + corePercentage: integer + pcieAllowList: + - string + pcieDenyList: + - string + pcieModel: string + driveSizeRange: string + socketsToUse: + - string + nodesPerSocket: integer + dataIfname: + - string + workerNodes: + - string + openShiftCluster: boolean + openShiftMachineConfigPool: string + deviceNames: + - string + ubuntuHost: boolean + skipKubeletConfiguration: boolean + forceFormat4K: boolean + enableCpuTopology: boolean + reservedSystemCPU: string + spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' + tolerations: + - Toleration + maxParallelNodeAdds: integer + containerResources: ResourceRequirements + initContainerResources: ResourceRequirements + imagePullPolicy: PullPolicy + nodeFailureDomains: + string: integer + expand: boolean + nodeConfigs: + string: + maxLogicalVolumeCount: integer + maxSize: string + spdkImage: string + spdkProxyImage: string + corePercentage: integer + spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' + journalManager: + count: integer + percentPerDevice: integer + pcieAllowList: + - string + pcieDenyList: + - string + pcieModel: string + driveSizeRange: string + deviceNames: + - string + enableCpuTopology: boolean + reservedSystemCPU: string + ubuntuHost: boolean + skipKubeletConfiguration: boolean + failureDomain: integer + expand: boolean +status: + totalNodes: integer + onlineNodes: integer + offlineNodes: integer + suspendedNodes: integer + creatingNodes: integer + removedNodes: integer + nodes: + - uuid: string + health: boolean + status: string + cpu: integer + memory: string + volumes: integer + rpcPort: integer + lvolPort: integer + nvmfPort: integer + devices: string + uptime: string + hostname: string + mgmtIp: string + postedAt: Time + drainCoordination: + - hostname: string + phase: string + startedAt: Time + message: string + activeNodeUUID: string + pendingNodeAdds: + string: Time + schedulingFailedWorkers: + string: boolean + latencyMetrics: + - nodeUUID: string + baselineP50NS: integer + baselineP99NS: integer + baselineMeasuredAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `StorageNodeSet` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[StorageNodeSetSpec](#storagenodesetspec)_ | spec defines the desired state of StorageNodeSet | | Required: \{\}
| +| `status` _[StorageNodeSetStatus](#storagenodesetstatus)_ | status defines the observed state of StorageNodeSet | | Optional: \{\}
| + + +#### StorageNodeSetSpec + + + +StorageNodeSetSpec defines the desired state of StorageNodeSet + + + +_Appears in:_ +- [StorageNodeSet](#storagenodeset) + +_Example:_ + +```yaml +clusterName: string +clusterImage: '^($|(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})?)$' +maxLogicalVolumeCount: integer +maxSize: string +spdkImage: '^($|(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})?)$' +spdkProxyImage: '^($|(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})?)$' +mgmtIfname: string +partitions: integer +journalManager: + count: integer + percentPerDevice: integer +corePercentage: integer +pcieAllowList: + - string +pcieDenyList: + - string +pcieModel: string +driveSizeRange: string +socketsToUse: + - string +nodesPerSocket: integer +dataIfname: + - string +workerNodes: + - string +openShiftCluster: boolean +openShiftMachineConfigPool: string +deviceNames: + - string +ubuntuHost: boolean +skipKubeletConfiguration: boolean +forceFormat4K: boolean +enableCpuTopology: boolean +reservedSystemCPU: string +spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' +tolerations: + - Toleration +maxParallelNodeAdds: integer +containerResources: ResourceRequirements +initContainerResources: ResourceRequirements +imagePullPolicy: PullPolicy +nodeFailureDomains: + string: integer +expand: boolean +nodeConfigs: + string: + maxLogicalVolumeCount: integer + maxSize: string + spdkImage: string + spdkProxyImage: string + corePercentage: integer + spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' + journalManager: + count: integer + percentPerDevice: integer + pcieAllowList: + - string + pcieDenyList: + - string + pcieModel: string + driveSizeRange: string + deviceNames: + - string + enableCpuTopology: boolean + reservedSystemCPU: string + ubuntuHost: boolean + skipKubeletConfiguration: boolean + failureDomain: integer + expand: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `clusterImage` _string_ | ClusterImage is the container image used for storage-node workloads.
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\})?)$`
| +| `maxLogicalVolumeCount` _integer_ | MaxLogicalVolumeCount is the maximum number of logical volumes per node. | | | +| `maxSize` _string_ | MaxSize is the maximum allocatable size of huge pages. | | | +| `spdkImage` _string_ | SpdkImage is the SPDK image reference used by node services.
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\})?)$`
| +| `spdkProxyImage` _string_ | SpdkProxyImage is the SPDK proxy image reference used by node services.
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\})?)$`
| +| `mgmtIfname` _string_ | MgmtIfname is the management interface name used by storage nodes. | | | +| `partitions` _integer_ | Partitions is the number of partitions created per backend storage device. | | | +| `journalManager` _[JournalManagerSpec](#journalmanagerspec)_ | JournalManagerSpec configures journal manager behavior. | | | +| `corePercentage` _integer_ | CorePercentage is the percentage of cores to be used for spdk (0-99). | | | +| `pcieAllowList` _string array_ | PcieAllowList is the list of PCI addresses allowed for use. | | | +| `pcieDenyList` _string array_ | PcieDenyList is the list of PCI addresses excluded from use. | | | +| `pcieModel` _string_ | PcieModel filters devices by PCI model. | | | +| `driveSizeRange` _string_ | DriveSizeRange filters devices by size range. | | | +| `socketsToUse` _string array_ | SocketsToUse restricts deployment to selected NUMA sockets. | | | +| `nodesPerSocket` _integer_ | NodesPerSocket defines how many storage nodes are created per NUMA socket. | | | +| `dataIfname` _string array_ | DataIfname lists data-plane network interfaces. | | | +| `workerNodes` _string array_ | WorkerNodes is the set of Kubernetes worker nodes to manage. | | MaxItems: 200
| +| `openShiftCluster` _boolean_ | OpenShiftCluster indicates OpenShift-specific behavior should be enabled. | | | +| `openShiftMachineConfigPool` _string_ | OpenShiftMachineConfigPool is the name of the MachineConfigPool that storage nodes belong to.
Generated MachineConfig resources will carry the machineconfiguration.openshift.io/role label
set to this value. Defaults to "worker" when unset. | | | +| `deviceNames` _string array_ | DeviceNames explicitly defines a comma separated list of nvme namespace names like nvme0n1,nvme1n1... | | | +| `ubuntuHost` _boolean_ | UbuntuHost indicates the node host OS is Ubuntu. | | | +| `skipKubeletConfiguration` _boolean_ | SkipKubeletConfiguration skips kubelet configuration changes. | | | +| `forceFormat4K` _boolean_ | ForceFormat4K forces 4K blocksize formatting of the NVMe device where supported. | | | +| `enableCpuTopology` _boolean_ | EnableCpuTopology enables topology-aware CPU handling. | | | +| `reservedSystemCPU` _string_ | ReservedSystemCPU defines CPUs reserved for system workloads. | | | +| `spdkSystemMemory` _string_ | SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M").
When omitted the backend default is used. | | Pattern: `^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$`
| +| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#toleration-v1-core) array_ | Tolerations configures pod tolerations for storage-node pods. | | | +| `maxParallelNodeAdds` _integer_ | MaxParallelNodeAdds limits how many non-FDB worker nodes can be in the
add process simultaneously. Defaults to 1 (fully sequential).
FDB workers are always sequential regardless of this setting. | 1 | Minimum: 1
| +| `containerResources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#resourcerequirements-v1-core)_ | ContainerResources sets CPU and memory requests/limits for the main storage-node container.
When omitted no limits are enforced, which preserves the previous behaviour. | | | +| `initContainerResources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#resourcerequirements-v1-core)_ | InitContainerResources sets CPU and memory requests/limits for the init container.
When omitted no limits are enforced. | | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#pullpolicy-v1-core)_ | ImagePullPolicy controls when the container image is pulled. Defaults to IfNotPresent. | | Enum: [Always Never IfNotPresent]
| +| `nodeFailureDomains` _object (keys:string, values:integer)_ | NodeFailureDomains assigns each worker node to a failure-domain group (integer ≥ 1).
Required when the referenced StorageCluster has enableFailureDomains=true.
Keys are Kubernetes worker node names; values are the failure-domain group index.
Each node in the same physical failure domain (rack, AZ, power unit) should share
the same group index so the control plane can spread erasure-coding chunks across
independent fault groups. | | Optional: \{\}
| +| `expand` _boolean_ | Expand indicates that storage nodes added from this StorageNodeSet are being
added to expand an already-active cluster. When true the backend node-add
endpoint receives expand=true, which triggers the appropriate rebalancing
behaviour for in-place cluster growth. | | Optional: \{\}
| +| `nodeConfigs` _object (keys:string, values:[StorageNodeOverrides](#storagenodeoverrides))_ | NodeConfigs allows per-worker-node configuration overrides keyed by the
Kubernetes worker node name. Entries are propagated to the corresponding
StorageNode.spec.overrides by the StorageNodeReconciler on every reconcile.
The StorageNodeSet is the single source of truth for all per-node config,
including failure domain assignment via nodeConfigs[worker].failureDomain. | | MaxProperties: 200
Optional: \{\}
| + + +#### StorageNodeSetStatus + + + +StorageNodeSetStatus defines the observed state of StorageNodeSet. + + + +_Appears in:_ +- [StorageNodeSet](#storagenodeset) + +_Example:_ + +```yaml +totalNodes: integer +onlineNodes: integer +offlineNodes: integer +suspendedNodes: integer +creatingNodes: integer +removedNodes: integer +nodes: + - uuid: string + health: boolean + status: string + cpu: integer + memory: string + volumes: integer + rpcPort: integer + lvolPort: integer + nvmfPort: integer + devices: string + uptime: string + hostname: string + mgmtIp: string + postedAt: Time +drainCoordination: + - hostname: string + phase: string + startedAt: Time + message: string + activeNodeUUID: string +pendingNodeAdds: + string: Time +schedulingFailedWorkers: + string: boolean +latencyMetrics: + - nodeUUID: string + baselineP50NS: integer + baselineP99NS: integer + baselineMeasuredAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `totalNodes` _integer_ | TotalNodes is the total number of owned StorageNode CRs. | | Optional: \{\}
| +| `onlineNodes` _integer_ | OnlineNodes is the count of StorageNode CRs with status "online". | | Optional: \{\}
| +| `offlineNodes` _integer_ | OfflineNodes is the count of StorageNode CRs with status "offline". | | Optional: \{\}
| +| `suspendedNodes` _integer_ | SuspendedNodes is the count of StorageNode CRs with status "suspended". | | Optional: \{\}
| +| `creatingNodes` _integer_ | CreatingNodes is the count of StorageNode CRs with status "in_creation". | | Optional: \{\}
| +| `removedNodes` _integer_ | RemovedNodes is the count of StorageNode CRs with status "removed". | | Optional: \{\}
| +| `nodes` _[NodeStatus](#nodestatus) array_ | Nodes is the observed state of each managed storage node. | | | +| `drainCoordination` _[NodeDrainState](#nodedrainstate) array_ | DrainCoordination tracks the upgrade-drain state per worker node. | | | +| `pendingNodeAdds` _object (keys:string, values:[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta))_ | PendingNodeAdds records the timestamp when a node-add POST was sent for
each worker hostname. Entries are removed only when all socket nodes for
that worker come online. This is the authoritative guard against duplicate
POSTs — it is a separate map field so patches to Status.Nodes never
inadvertently delete it. | | | +| `schedulingFailedWorkers` _object (keys:string, values:boolean)_ | SchedulingFailedWorkers tracks worker hostnames whose SPDK pod experienced
a FailedScheduling event during node add. Used to emit a recovery event
when the node subsequently comes online. | | | +| `latencyMetrics` _[NodeLatencyMetrics](#nodelatencymetrics) array_ | LatencyMetrics holds per-backend-node fio-measured latency data for rebalancing decisions. | | | + + +#### StorageNodeSpec + + + +StorageNodeSpec defines the desired state of a StorageNode. + + + +_Appears in:_ +- [StorageNode](#storagenode) + +_Example:_ + +```yaml +storageNodeSetRef: string +workerNode: string +socketId: string +nodeIndex: integer +socketIndex: integer +overrides: + maxLogicalVolumeCount: integer + maxSize: string + spdkImage: string + spdkProxyImage: string + corePercentage: integer + spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$' + journalManager: + count: integer + percentPerDevice: integer + pcieAllowList: + - string + pcieDenyList: + - string + pcieModel: string + driveSizeRange: string + deviceNames: + - string + enableCpuTopology: boolean + reservedSystemCPU: string + ubuntuHost: boolean + skipKubeletConfiguration: boolean + failureDomain: integer + expand: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `storageNodeSetRef` _string_ | StorageNodeSetRef is the name of the owning StorageNodeSet. Immutable. | | Required: \{\}
| +| `workerNode` _string_ | WorkerNode is the Kubernetes node hostname this StorageNode runs on.
Users may not change it directly — it is re-pointed only by the operator
during a node migration (StorageNodeOps action=migrate). The
StorageNode validating webhook rejects user-driven changes to this field. | | Required: \{\}
| +| `socketId` _string_ | SocketID is the NUMA socket identifier from spec.socketsToUse (e.g. "0", "1"). Immutable. | | Optional: \{\}
| +| `nodeIndex` _integer_ | NodeIndex is the per-socket node index (0..nodesPerSocket-1). Immutable. | | Optional: \{\}
| +| `socketIndex` _integer_ | SocketIndex is the global ordinal (socketPosition × nodesPerSocket + nodeIndex).
Used internally by the operator to select the correct backend node from the
RPC-port-sorted list in pollUUIDFromBackend. Immutable. | | Optional: \{\}
| +| `overrides` _[StorageNodeOverrides](#storagenodeoverrides)_ | Overrides holds per-node configuration propagated from
StorageNodeSet.spec.nodeConfigs[workerNode] on every reconcile. | | Optional: \{\}
| + + +#### StorageNodeStatus + + + +StorageNodeStatus holds the observed state of a StorageNode. + + + +_Appears in:_ +- [StorageNode](#storagenode) + +_Example:_ + +```yaml +uuid: string +status: string +health: boolean +hostname: string +uptime: string +resources: + cpu: integer + memory: string + volumes: integer + devices: string +ports: + management: string + nvmeof: integer + lvol: integer + rpc: integer +postedAt: Time +activeOpsRef: string +latencyMetrics: + nodeUUID: string + baselineP50NS: integer + baselineP99NS: integer + baselineMeasuredAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `uuid` _string_ | UUID is the backend storage node UUID. Set once after node-add completes. | | Optional: \{\}
| +| `status` _string_ | Status is the backend-reported node status (e.g. online, suspended, offline). | | Optional: \{\}
| +| `health` _boolean_ | Health is the backend-reported node health flag. | | Optional: \{\}
| +| `hostname` _string_ | Hostname is the node hostname as reported by the backend. | | Optional: \{\}
| +| `uptime` _string_ | Uptime is the node uptime as reported by the backend. | | Optional: \{\}
| +| `resources` _[StorageNodeResources](#storagenoderesources)_ | Resources groups compute and storage resource metrics. | | Optional: \{\}
| +| `ports` _[StorageNodePorts](#storagenodeports)_ | Ports groups network connectivity fields (addresses and ports). | | Optional: \{\}
| +| `postedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | PostedAt is the timestamp when the node-add POST was sent.
Used as a provisioning guard against duplicate POSTs. | | Optional: \{\}
| +| `activeOpsRef` _string_ | ActiveOpsRef is the name of the currently active StorageNodeOps CR targeting
this node. Empty when no operation is in progress. Used for mutual exclusion. | | Optional: \{\}
| +| `latencyMetrics` _[NodeLatencyMetrics](#nodelatencymetrics)_ | LatencyMetrics holds the fio-measured baseline NVMe-oF latency for this node,
used by the volume rebalancer to make data-placement decisions. | | Optional: \{\}
| + + +#### StripeSpec + + + + + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +dataChunks: integer +parityChunks: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `dataChunks` _integer_ | DataChunks defines the number of data chunks in the erasure-coding layout. | | | +| `parityChunks` _integer_ | ParityChunks defines the number of parity chunks in the erasure-coding layout. | | | + + +#### Task + + + +Task is the Schema for the tasks API + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: Task +metadata: + name: string +spec: + clusterName: string + taskID: string + subtasks: boolean +status: + tasks: + - uuid: string + taskType: string + taskStatus: string + taskResult: string + canceled: boolean + parentTask: string + startedAt: Time + retried: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `Task` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[TaskSpec](#taskspec)_ | spec defines the desired state of Task | | Required: \{\}
| +| `status` _[TaskStatus](#taskstatus)_ | status defines the observed state of Task | | Optional: \{\}
| + + +#### TaskEntry + + + + + + + +_Appears in:_ +- [TaskStatus](#taskstatus) + +_Example:_ + +```yaml +uuid: string +taskType: string +taskStatus: string +taskResult: string +canceled: boolean +parentTask: string +startedAt: Time +retried: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `uuid` _string_ | UUID is the backend task UUID. | | | +| `taskType` _string_ | TaskType is the backend task function/type name. | | | +| `taskStatus` _string_ | TaskStatus is the backend lifecycle status for the task. | | | +| `taskResult` _string_ | TaskResult is the backend result payload/message. | | | +| `canceled` _boolean_ | Canceled indicates whether the task was canceled. | | | +| `parentTask` _string_ | ParentTask is the parent task UUID when this task is a subtask.
FIXME: Unused for now | | | +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | StartedAt is the backend-reported task start timestamp.
FIXME: Unused for now | | | +| `retried` _integer_ | Retried is the number of retry attempts made for the task. | | | + + +#### TaskSpec + + + +TaskSpec defines the desired state of Task + + + +_Appears in:_ +- [Task](#task) + +_Example:_ + +```yaml +clusterName: string +taskID: string +subtasks: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clusterName` _string_ | ClusterName is the target storage cluster name. | | | +| `taskID` _string_ | TaskID filters results to a specific backend task when set. | | | +| `subtasks` _boolean_ | Subtasks includes related child subtasks when supported by the backend.
FIXME: Unused for now | | | + + +#### TaskStatus + + + +TaskStatus defines the observed state of Task. + + + +_Appears in:_ +- [Task](#task) + +_Example:_ + +```yaml +tasks: + - uuid: string + taskType: string + taskStatus: string + taskResult: string + canceled: boolean + parentTask: string + startedAt: Time + retried: integer +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `tasks` _[TaskEntry](#taskentry) array_ | Tasks is the currently reported task list for the query scope. | | | + + +#### VolumeAutoPlacementSettings + + + +VolumeAutoPlacementSettings controls the automatic, latency-driven volume rebalancing +behaviour. It is configured under StorageClusterSpec.VolumeAutoPlacement. + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +enabled: boolean +migrationEnabled: boolean +evaluationInterval: Duration +imbalanceThreshold: integer +minHotColdDifferencePct: integer +defaultCoolDownSeconds: integer +maxVolumeMigrationsPerCycle: integer +storageNodeCandidateCount: integer +metricsBackend: MetricsBackend +prometheusURL: string +latencyBenchmarkEnabled: boolean +latencyBenchmarkInterval: Duration +iopsWeight: float +throughputWeight: float +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enabled` _boolean_ | Enabled activates automatic rebalancing for this cluster. Defaults to false. | | Optional: \{\}
| +| `migrationEnabled` _boolean_ | MigrationEnabled controls whether the rebalancer actually creates VolumeMigration
CRs. When false the rebalancer still runs every cycle — evaluating load, computing
deviations, selecting candidates and emitting metrics — but discards the migrations
instead of creating them (dry-run). Defaults to true. | | Optional: \{\}
| +| `evaluationInterval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#duration-v1-meta)_ | EvaluationInterval is how often the rebalancer evaluates load. Defaults to 60s. | | Optional: \{\}
| +| `imbalanceThreshold` _integer_ | ImbalanceThreshold is the minimum latency deviation from baseline (in percent)
that a node must exhibit before it is considered a rebalancing source. Defaults to 80. | | Optional: \{\}
| +| `minHotColdDifferencePct` _integer_ | MinHotColdDifferencePct is the minimum latency-deviation gap (in percentage points)
that a candidate target node must be below the hot source node before a migration is
performed. Prevents migrating between near-equally-loaded nodes. Defaults to 20. | | Optional: \{\}
| +| `defaultCoolDownSeconds` _integer_ | DefaultCoolDownSeconds is the cool-down period (seconds) applied to a volume after
it has been migrated. Defaults to 600. | | Optional: \{\}
| +| `maxVolumeMigrationsPerCycle` _integer_ | MaxVolumeMigrationsPerCycle is the maximum number of volumes moved per cycle. Defaults to 10. | | Optional: \{\}
| +| `storageNodeCandidateCount` _integer_ | StorageNodeCandidateCount is the number of top-loaded nodes evaluated each cycle to
find the best migration source. Defaults to 3. | | Optional: \{\}
| +| `metricsBackend` _[MetricsBackend](#metricsbackend)_ | MetricsBackend selects the data source for I/O metrics. Defaults to "prometheus". | | Enum: [controlplane prometheus uniform]
Optional: \{\}
| +| `prometheusURL` _string_ | PrometheusURL is required when MetricsBackend is "prometheus". | | Optional: \{\}
| +| `latencyBenchmarkEnabled` _boolean_ | LatencyBenchmarkEnabled enables fio-based NVMe-oF latency measurement via Kubernetes Jobs.
Defaults to false; set to true once a RebalancerImage is configured. | | Optional: \{\}
| +| `latencyBenchmarkInterval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#duration-v1-meta)_ | LatencyBenchmarkInterval is how often fio benchmark Jobs run against each storage node.
Defaults to 5m. | | Optional: \{\}
| +| `iopsWeight` _float_ | IOPSWeight is the weight applied to per-volume IOPS in the volume IO score. Defaults to 1.0. | | Optional: \{\}
| +| `throughputWeight` _float_ | ThroughputWeight is the weight applied to per-volume throughput (MB/s) in the volume
IO score. Defaults to 0.1. | | Optional: \{\}
| + + +#### VolumeMigration + + + +VolumeMigration triggers a storage-node migration for a single PersistentVolume. +Create a VolumeMigration to move a volume's backing logical volume to a different +storage node. The controller resolves the PV to a logical volume UUID, submits the +migration via the storage API, and tracks progress until completion or failure. +Set spec.abort=true to cancel an in-progress migration. + + + + + +_Example:_ + +```yaml +apiVersion: storage.simplyblock.io/v1alpha1 +kind: VolumeMigration +metadata: + name: string +spec: + pvName: string + targetNodeUUID: string + abort: boolean +status: + phase: VolumeMigrationPhase + migrationUUID: string + clusterUUID: string + volumeUUID: string + poolUUID: string + sourceNodeUUID: string + snapsTotal: integer + snapsMigrated: integer + errorMessage: string + connections: + - nqn: string + ip: string + port: integer + transport: string + nrIoQueues: integer + reconnectDelay: integer + ctrlLossTmo: integer + fastIOFailTmo: integer + keepAliveTmo: integer + validationJobName: string + startedAt: Time + completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `storage.simplyblock.io/v1alpha1` | | | +| `kind` _string_ | `VolumeMigration` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[VolumeMigrationSpec](#volumemigrationspec)_ | | | | +| `status` _[VolumeMigrationStatus](#volumemigrationstatus)_ | | | | + + +#### VolumeMigrationPhase + +_Underlying type:_ _string_ + +VolumeMigrationPhase describes the lifecycle state of a VolumeMigration. + + + +_Appears in:_ +- [VolumeMigrationStatus](#volumemigrationstatus) + +| Field | Description | +| --- | --- | +| `Pending` | VolumeMigrationPhasePending means the migration has been accepted but not
yet submitted to the storage API.
| +| `Validating` | VolumeMigrationPhaseValidating means CreateMigration has been called and
the operator is validating the new NVMe-oF connection paths on the target
node before calling ContinueMigration.
| +| `Running` | VolumeMigrationPhaseRunning means ContinueMigration has been called and
the data migration is in progress.
| +| `Completed` | VolumeMigrationPhaseCompleted means the migration finished successfully.
| +| `Failed` | VolumeMigrationPhaseFailed means the migration finished with an error.
| +| `Aborted` | VolumeMigrationPhaseAborted means the migration was cancelled via spec.abort.
| + + +#### VolumeMigrationSettings + + + +VolumeMigrationSettings carries cluster-level settings for volume migration. +Automatic load-based rebalancing is configured separately via +StorageClusterSpec.VolumeAutoPlacement, keeping the manual-migration controls +separate from the rebalancing policy. + + + +_Appears in:_ +- [StorageClusterSpec](#storageclusterspec) + +_Example:_ + +```yaml +enabled: boolean +rebalancerImage: string +dataRealignment: + enabled: boolean + interval: Duration +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enabled` _boolean_ | Enabled turns on volume migration for this cluster. When false, the operator
will not act on VolumeMigration resources for this cluster. Defaults to true. | | Optional: \{\}
| +| `rebalancerImage` _string_ | RebalancerImage is the container image used for the volume-migration path
validation Job and the rebalancer latency/baseline Jobs. The image must include
nvme-cli (and, for rebalancing, fio + jq). | | Optional: \{\}
| +| `dataRealignment` _[DataRealignmentSettings](#datarealignmentsettings)_ | DataRealignment configures the periodic control-plane data realignment that
runs after volumes have been moved. Realignment re-aligns the cluster's internal
data structures to the current volume placement so fault-tolerance (FTT) and
node-affinity guarantees are preserved. It applies to *all* volume moves —
auto-rebalancing, manual VolumeMigrations, and drain/removal-triggered moves —
so it lives here rather than under AutoRebalancing. Enabled by default. | | Optional: \{\}
| + + +#### VolumeMigrationSpec + + + +VolumeMigrationSpec defines the desired state of a VolumeMigration. + + + +_Appears in:_ +- [VolumeMigration](#volumemigration) + +_Example:_ + +```yaml +pvName: string +targetNodeUUID: string +abort: boolean +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `pvName` _string_ | PVName is the name of the PersistentVolume whose backing logical volume
should be migrated. The PV must be provisioned by the simplyblock CSI driver. | | MinLength: 1
| +| `targetNodeUUID` _string_ | TargetNodeUUID is the UUID of the storage node that should host the
volume after migration. | | MinLength: 1
| +| `abort` _boolean_ | Abort requests cancellation of an in-progress migration. Set to true to
cancel; the phase will transition to Aborted once the backend confirms. | | Optional: \{\}
| + + +#### VolumeMigrationStatus + + + +VolumeMigrationStatus defines the observed state of a VolumeMigration. + + + +_Appears in:_ +- [VolumeMigration](#volumemigration) + +_Example:_ + +```yaml +phase: VolumeMigrationPhase +migrationUUID: string +clusterUUID: string +volumeUUID: string +poolUUID: string +sourceNodeUUID: string +snapsTotal: integer +snapsMigrated: integer +errorMessage: string +connections: + - nqn: string + ip: string + port: integer + transport: string + nrIoQueues: integer + reconnectDelay: integer + ctrlLossTmo: integer + fastIOFailTmo: integer + keepAliveTmo: integer +validationJobName: string +startedAt: Time +completedAt: Time +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `phase` _[VolumeMigrationPhase](#volumemigrationphase)_ | Phase is the current lifecycle phase of the migration. | | Enum: [Pending Validating Running Completed Failed Aborted]
| +| `migrationUUID` _string_ | MigrationUUID is the identifier returned by the storage API when the
migration was submitted. Used for polling and cancellation. | | | +| `clusterUUID` _string_ | ClusterUUID is the storage cluster UUID resolved from the PV. | | | +| `volumeUUID` _string_ | VolumeUUID is the logical volume UUID resolved from the PV's CSI volume handle. | | | +| `poolUUID` _string_ | PoolUUID is the storage pool UUID that contains the volume. | | | +| `sourceNodeUUID` _string_ | SourceNodeUUID is the storage node UUID where the volume resided before
migration, as reported by the storage API. | | | +| `snapsTotal` _integer_ | SnapsTotal is the total number of snapshots to migrate, as reported by the API. | | | +| `snapsMigrated` _integer_ | SnapsMigrated is the number of snapshots migrated so far. | | | +| `errorMessage` _string_ | ErrorMessage holds the failure reason when Phase is Failed. | | | +| `connections` _[MigrationConnection](#migrationconnection) array_ | Connections holds the NVMe-oF connection parameters for the new target-side
paths returned by CreateMigration. Used during the Validating phase to
establish and verify the paths before calling ContinueMigration. | | | +| `validationJobName` _string_ | ValidationJobName is the name of the Job that runs `nvme connect` for each
connection path and validates ANA state before ContinueMigration is called.
Set during the Validating phase; cleared when the phase advances to Running. | | | +| `startedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | StartedAt is the time the migration was submitted to the storage API. | | | +| `completedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | CompletedAt is the time the migration finished (successfully or not). | | | + + +#### VolumeReplicationStatus + + + +VolumeReplicationStatus tracks the replication state of an individual volume + + + +_Appears in:_ +- [SnapshotReplicationStatus](#snapshotreplicationstatus) + +_Example:_ + +```yaml +volumeID: string +phase: string +lastSnapshotID: string +lastReplicationTime: Time +replicatedCount: integer +errors: + - timestamp: Time + message: string +``` + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `volumeID` _string_ | Volume ID | | | +| `phase` _string_ | Phase is the current replication phase for this volume. | | Enum: [Pending Running TriggeringTargetReplication WaitingForTargetReplication ReplicatingToSource WaitingForTargetDeletion Completed Failed Paused]
| +| `lastSnapshotID` _string_ | Last snapshot ID replicated for this volume | | | +| `lastReplicationTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.36/#time-v1-meta)_ | Timestamp of the last successful replication for this volume | | | +| `replicatedCount` _integer_ | Number of snapshots successfully replicated | | | +| `errors` _[ReplicationError](#replicationerror) array_ | Optional: list of errors encountered for this volume | | | + + diff --git a/docs/usage/backup-recovery.md b/docs/usage/backup-recovery.md index 156e43f8..c44f4f8a 100644 --- a/docs/usage/backup-recovery.md +++ b/docs/usage/backup-recovery.md @@ -225,7 +225,7 @@ spec: withCompression: false ``` -See the [Operator Reference](../reference/operator.md#storage-cluster) for all available `backup` spec fields. +See the [Operator Reference](../reference/operator/reference.md#storagecluster) for all available `backup` spec fields. ### StorageBackup CRD diff --git a/scripts/operator-crd-ref-docs.yaml b/scripts/operator-crd-ref-docs.yaml new file mode 100644 index 00000000..9da1e076 --- /dev/null +++ b/scripts/operator-crd-ref-docs.yaml @@ -0,0 +1,10 @@ +processor: + ignoreTypes: + - ".*List$" + ignoreFields: + - "TypeMeta$" + - "ObjectMeta$" + +render: + showExampleYAML: true + kubernetesVersion: 1.36 diff --git a/scripts/operator-lock.sh b/scripts/operator-lock.sh new file mode 100755 index 00000000..3170b261 --- /dev/null +++ b/scripts/operator-lock.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Resolves the latest matching simplyblock-operator release tag for a given +# sbcli version and writes it to scripts/operator.lock (next to sbcli.lock). +# +# "Latest matching" means the operator tag whose MAJOR.MINOR equals the sbcli +# version's MAJOR.MINOR, with the highest patch level. For example, an sbcli +# version of 26.2.4 resolves to the newest operator tag v26.2.y (e.g. v26.2.8). +# +# Usage: +# ./scripts/operator-lock.sh [] +# +# If no version is passed, it is read from scripts/sbcli.lock. If operator.lock +# already exists, it is left untouched. If no matching operator tag exists (for +# example for older releases that predate the operator), the lock is skipped +# without failing. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OPERATOR_REPO_URL="${OPERATOR_REPO_URL:-https://github.com/simplyblock/simplyblock-operator.git}" +OPERATOR_LOCK="${OPERATOR_LOCK:-${SCRIPT_DIR}/operator.lock}" +SBCLI_LOCK="${SBCLI_LOCK:-${SCRIPT_DIR}/sbcli.lock}" + +VERSION="${1:-}" +if [ -z "${VERSION}" ] && [ -f "${SBCLI_LOCK}" ]; then + VERSION="$(cat "${SBCLI_LOCK}")" +fi + +if [ -z "${VERSION}" ]; then + echo "No sbcli version supplied and no ${SBCLI_LOCK} present." >&2 + exit 1 +fi + +if [ -f "${OPERATOR_LOCK}" ]; then + echo "operator.lock already present ($(cat "${OPERATOR_LOCK}")), leaving untouched." + exit 0 +fi + +MAJOR_MINOR="$(printf '%s' "${VERSION}" | grep -oE '^[0-9]+\.[0-9]+' || true)" +if [ -z "${MAJOR_MINOR}" ]; then + echo "Could not derive MAJOR.MINOR from sbcli version '${VERSION}'." >&2 + exit 1 +fi + +# Escape dots so they are matched literally in the tag regex. +MM_REGEX="$(printf '%s' "${MAJOR_MINOR}" | sed 's/\./\\./g')" + +echo "Resolving latest simplyblock-operator tag matching v${MAJOR_MINOR}.* ..." +OPERATOR_TAG="$(git ls-remote --tags --refs "${OPERATOR_REPO_URL}" \ + | sed -E 's#.*refs/tags/##' \ + | grep -E "^v${MM_REGEX}\.[0-9]+$" \ + | sort -V \ + | tail -n1 || true)" + +if [ -z "${OPERATOR_TAG}" ]; then + echo "No simplyblock-operator release tag found matching v${MAJOR_MINOR}.* — skipping operator lock." >&2 + exit 0 +fi + +echo "Locking simplyblock-operator to ${OPERATOR_TAG}" +printf '%s\n' "${OPERATOR_TAG}" > "${OPERATOR_LOCK}" diff --git a/scripts/operator-reference-gen.sh b/scripts/operator-reference-gen.sh new file mode 100755 index 00000000..cbb028dc --- /dev/null +++ b/scripts/operator-reference-gen.sh @@ -0,0 +1,71 @@ + +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DOCS_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Prefer the operator checkout managed by `./doc-builder update-repositories` +# (pinned via scripts/operator.lock). Fall back to a sibling checkout for local +# development. Override explicitly by setting OPERATOR_ROOT. +if [ -z "${OPERATOR_ROOT:-}" ]; then + if [ -d "${DOCS_ROOT}/scripts/operator-repo" ]; then + OPERATOR_ROOT="${DOCS_ROOT}/scripts/operator-repo" + else + OPERATOR_ROOT="$(cd "${DOCS_ROOT}/../simplyblock-manager" && pwd)" + fi +fi +OUTPUT_FILE="${OUTPUT_FILE:-${DOCS_ROOT}/docs/reference/operator/reference.md}" +CONFIG_FILE="${CONFIG_FILE:-${SCRIPT_DIR}/operator-crd-ref-docs.yaml}" + +# The operator Go API types live under the operator/ subdirectory in recent +# versions (operator/api), while older tags kept them at the repository root +# (api). Prefer the new layout and fall back to the legacy one. Override +# explicitly by setting OPERATOR_API_PATH. +if [ -n "${OPERATOR_API_PATH:-}" ]; then + SOURCE_PATH="${OPERATOR_API_PATH}" +elif [ -d "${OPERATOR_ROOT}/operator/api" ]; then + SOURCE_PATH="${OPERATOR_ROOT}/operator/api" +elif [ -d "${OPERATOR_ROOT}/api" ]; then + SOURCE_PATH="${OPERATOR_ROOT}/api" +else + echo "Could not find operator API types under ${OPERATOR_ROOT} (looked for operator/api and api)." >&2 + exit 1 +fi +CRD_REF_DOCS_REPO="${CRD_REF_DOCS_REPO:-https://github.com/simplyblock/crd-ref-docs.git}" +CRD_REF_DOCS_REF="${CRD_REF_DOCS_REF:-master}" + +TMP_FILE="$(mktemp)" +CRD_REF_DOCS_DIR="$(mktemp -d)" +CRD_REF_DOCS_BIN="$(mktemp -u)" +trap 'rm -f "${TMP_FILE}" "${CRD_REF_DOCS_BIN}"; rm -rf "${CRD_REF_DOCS_DIR}"' EXIT + +git clone --quiet --depth 1 --branch "${CRD_REF_DOCS_REF}" \ + "${CRD_REF_DOCS_REPO}" "${CRD_REF_DOCS_DIR}" + +go build -C "${CRD_REF_DOCS_DIR}" -o "${CRD_REF_DOCS_BIN}" . + +"${CRD_REF_DOCS_BIN}" \ + --source-path "${SOURCE_PATH}" \ + --config "${CONFIG_FILE}" \ + --renderer markdown \ + --output-path "${TMP_FILE}" + +{ + cat <<'FRONTMATTER' +--- +title: "Simplyblock Operator Reference" +description: "Reference for Simplyblock operator Custom Resource Definitions (CRDs)." +weight: 20091 +--- + + + +FRONTMATTER + cat "${TMP_FILE}" +} > "${OUTPUT_FILE}" + +echo "Generated ${OUTPUT_FILE}"