mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 18:44:33 +00:00
Add storage ops for deterministic namespace+cluster local PV paths, labels/selectors, host-path preparation, and idempotent reconciliation before CNPG cluster apply. Wire selector injection and validation into CNPG deploy flow/script, and extend installer tests for provisioning orchestration and failure handling. Co-authored-by: Junie <junie@jetbrains.com>
190 lines
6.7 KiB
Python
190 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from knoe.core.ops import storage as storage_ops
|
|
|
|
|
|
def test_build_cluster_storage_paths_prefers_distinct_roots(monkeypatch):
|
|
monkeypatch.setattr(
|
|
storage_ops,
|
|
"_is_non_root_mount",
|
|
lambda path, **_kwargs: str(path) in {"/synology/d001", "/synology/d002"},
|
|
)
|
|
|
|
spec = storage_ops.ClusterStorageSpec(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
node_name="myrddin.prole.org",
|
|
synology_roots=("/synology/d001", "/synology/d002"),
|
|
)
|
|
|
|
paths = storage_ops.build_cluster_storage_paths(spec)
|
|
|
|
assert paths.data_root != paths.wal_root
|
|
assert paths.data_path.endswith("/knoe/knoe-db/knoe-db/cluster-a/data")
|
|
assert paths.wal_path.endswith("/knoe/knoe-db/knoe-db/cluster-a/wal")
|
|
|
|
|
|
def test_build_cluster_storage_paths_falls_back_to_same_root(monkeypatch):
|
|
monkeypatch.setattr(
|
|
storage_ops,
|
|
"_is_non_root_mount",
|
|
lambda path, **_kwargs: str(path) == "/synology/d001",
|
|
)
|
|
|
|
spec = storage_ops.ClusterStorageSpec(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
node_name="myrddin.prole.org",
|
|
synology_roots=("/synology/d001", "/synology/d002"),
|
|
)
|
|
|
|
paths = storage_ops.build_cluster_storage_paths(spec)
|
|
|
|
assert paths.data_root == "/synology/d001"
|
|
assert paths.wal_root == "/synology/d001"
|
|
assert paths.data_path != paths.wal_path
|
|
|
|
|
|
def test_build_pv_labels_include_cluster_scope():
|
|
labels = storage_ops.build_pv_labels(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
role="data",
|
|
volume="d001",
|
|
)
|
|
|
|
assert labels["synology.storage/role"] == "data"
|
|
assert labels["synology.storage/volume"] == "d001"
|
|
assert labels["prole.io/namespace"] == "knoe-db"
|
|
assert labels["prole.io/cluster"] == "cluster-a"
|
|
assert labels["prole.io/service"] == "knoe-db"
|
|
|
|
|
|
def test_provision_cluster_storage_fails_on_existing_name_mismatch(monkeypatch):
|
|
spec = storage_ops.ClusterStorageSpec(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
node_name="myrddin.prole.org",
|
|
)
|
|
paths = storage_ops.ClusterStoragePaths(
|
|
data_root="/synology/d001",
|
|
wal_root="/synology/d002",
|
|
data_volume="d001",
|
|
wal_volume="d002",
|
|
data_path="/synology/d001/knoe/knoe-db/knoe-db/cluster-a/data",
|
|
wal_path="/synology/d002/knoe/knoe-db/knoe-db/cluster-a/wal",
|
|
)
|
|
|
|
data_name = storage_ops.build_pv_name("synology-iscsi", "knoe-db", "cluster-a", "data")
|
|
monkeypatch.setattr(storage_ops, "build_cluster_storage_paths", lambda _spec: paths)
|
|
monkeypatch.setattr(storage_ops, "ensure_host_path", lambda *args, **kwargs: None)
|
|
|
|
existing = {
|
|
data_name: {
|
|
"metadata": {
|
|
"name": data_name,
|
|
"labels": storage_ops.build_pv_labels(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
role="data",
|
|
volume="d001",
|
|
),
|
|
},
|
|
"spec": {
|
|
"storageClassName": "synology-iscsi",
|
|
"local": {"path": "/synology/d001/other-path"},
|
|
"nodeAffinity": {
|
|
"required": {
|
|
"nodeSelectorTerms": [
|
|
{
|
|
"matchExpressions": [
|
|
{
|
|
"key": "kubernetes.io/hostname",
|
|
"operator": "In",
|
|
"values": ["myrddin.prole.org"],
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
monkeypatch.setattr(storage_ops, "_collect_existing_pvs", lambda _spec: existing)
|
|
|
|
with pytest.raises(storage_ops.StorageProvisioningError, match="unexpected path"):
|
|
storage_ops.provision_cluster_storage(spec)
|
|
|
|
|
|
def test_provision_cluster_storage_applies_and_returns_selectors(monkeypatch):
|
|
spec = storage_ops.ClusterStorageSpec(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
node_name="myrddin.prole.org",
|
|
)
|
|
paths = storage_ops.ClusterStoragePaths(
|
|
data_root="/synology/d001",
|
|
wal_root="/synology/d002",
|
|
data_volume="d001",
|
|
wal_volume="d002",
|
|
data_path="/synology/d001/knoe/knoe-db/knoe-db/cluster-a/data",
|
|
wal_path="/synology/d002/knoe/knoe-db/knoe-db/cluster-a/wal",
|
|
)
|
|
|
|
monkeypatch.setattr(storage_ops, "build_cluster_storage_paths", lambda _spec: paths)
|
|
monkeypatch.setattr(storage_ops, "ensure_host_path", lambda *args, **kwargs: None)
|
|
monkeypatch.setattr(storage_ops, "_collect_existing_pvs", lambda _spec: {})
|
|
|
|
applied: list[dict] = []
|
|
|
|
def _capture_apply(_spec, manifest):
|
|
applied.append(manifest)
|
|
|
|
monkeypatch.setattr(storage_ops, "_kubectl_apply_manifest", _capture_apply)
|
|
|
|
provisioned = storage_ops.provision_cluster_storage(spec)
|
|
|
|
assert len(applied) == 2
|
|
assert provisioned.data_pv_name == "synology-iscsi-knoe-db-cluster-a-data"
|
|
assert provisioned.wal_pv_name == "synology-iscsi-knoe-db-cluster-a-wal"
|
|
assert provisioned.data_selector["prole.io/namespace"] == "knoe-db"
|
|
assert provisioned.data_selector["prole.io/cluster"] == "cluster-a"
|
|
assert provisioned.wal_selector["synology.storage/role"] == "wal"
|
|
|
|
|
|
def test_provision_cluster_storage_fails_on_path_overlap(monkeypatch):
|
|
spec = storage_ops.ClusterStorageSpec(
|
|
namespace="knoe-db",
|
|
cluster_name="cluster-a",
|
|
node_name="myrddin.prole.org",
|
|
)
|
|
paths = storage_ops.ClusterStoragePaths(
|
|
data_root="/synology/d001",
|
|
wal_root="/synology/d002",
|
|
data_volume="d001",
|
|
wal_volume="d002",
|
|
data_path="/synology/d001/knoe/knoe-db/knoe-db/cluster-a/data",
|
|
wal_path="/synology/d002/knoe/knoe-db/knoe-db/cluster-a/wal",
|
|
)
|
|
|
|
monkeypatch.setattr(storage_ops, "build_cluster_storage_paths", lambda _spec: paths)
|
|
monkeypatch.setattr(storage_ops, "ensure_host_path", lambda *args, **kwargs: None)
|
|
|
|
existing = {
|
|
"some-other-pv": {
|
|
"metadata": {"name": "some-other-pv", "labels": {}},
|
|
"spec": {
|
|
"storageClassName": "synology-iscsi",
|
|
"local": {"path": "/synology/d001/knoe/knoe-db/knoe-db/cluster-a"},
|
|
"nodeAffinity": {"required": {"nodeSelectorTerms": []}},
|
|
},
|
|
}
|
|
}
|
|
monkeypatch.setattr(storage_ops, "_collect_existing_pvs", lambda _spec: existing)
|
|
|
|
with pytest.raises(storage_ops.StorageProvisioningError, match="overlap"):
|
|
storage_ops.provision_cluster_storage(spec)
|