From 2cfa8fd446b1d08d7ce06636516ee85ea504baf8 Mon Sep 17 00:00:00 2001 From: Jeremy Harrington Date: Sun, 19 Jul 2026 17:14:20 -0700 Subject: [PATCH] feat(k8s): Longhorn Platform component (F1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `longhorn` to the opt-in Platform component set (chart `longhorn` from charts.longhorn.io, namespace longhorn-system): replicated block storage — RWO volumes with node-to-node replication + snapshots, the tier NFS/local-path don't cover. #Platform.longhorn? schema entry + unit test (chart/ns defaults, values thread-through). Every node needs open-iscsi, installable via a k3s worker pool's nodePrep.packages (E2) — documented in the component comment and schema — or Longhorn's iscsi-installer DaemonSet as a fallback. Opt-in like every component: platform.longhorn.enabled: true. --- ROADMAP.md | 14 +++++++--- plugins/k8s/internal/provider/platform.go | 6 +++++ .../k8s/internal/provider/platform_test.go | 27 +++++++++++++++++++ plugins/k8s/internal/provider/schema.go | 5 ++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index acfdbea..9bf1cb9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -184,10 +184,16 @@ prerequisite (Longhorn); the rest are convenience + robustness. the prereqs. **Makes F1 (Longhorn) usable.** ### F. Storage -- [ ] **F1 — Longhorn Platform component.** Add `longhorn` to the opt-in - Platform set (replicated block storage, the tier NFS/local-path don't - cover). Depends on E1 (open-iscsi on nodes); document the - iscsi-installer DaemonSet as the interim fallback. +- [x] **F1 — Longhorn Platform component.** `longhorn` added to the opt-in + Platform set (chart `longhorn` from `charts.longhorn.io`, ns + `longhorn-system`) — replicated block storage (RWO volumes with + node-to-node replication + snapshots), the tier NFS/local-path don't + cover. `#Platform.longhorn?` schema entry + unit test (chart/ns defaults, + values thread-through). Every node needs `open-iscsi`: install it via a + k3s worker pool's `nodePrep.packages` (**E2**, now shipped) — documented in + both the component comment and schema — or Longhorn's own iscsi-installer + DaemonSet as the interim fallback. Enabling is `platform.longhorn.enabled: + true`. ### G. Public exposure - [ ] **G1 — expose-app convenience.** A composite (or documented pattern) diff --git a/plugins/k8s/internal/provider/platform.go b/plugins/k8s/internal/provider/platform.go index 557e515..0f7d45f 100644 --- a/plugins/k8s/internal/provider/platform.go +++ b/plugins/k8s/internal/provider/platform.go @@ -52,6 +52,12 @@ var platformComponents = []component{ // persistent volumes on request. Set nfsProvisioner.values.nfs.{server,path} // to the NAS export. {"nfsProvisioner", "https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner", "nfs-subdir-external-provisioner", "nfs-provisioner"}, + // longhorn is replicated block storage — the tier NFS/local-path don't + // cover (RWO volumes with node-to-node replication + snapshots). Every node + // needs `open-iscsi` installed; provision it via a k3s worker pool's + // nodePrep (E2) — e.g. nodePrep.packages: ["open-iscsi"] — or Longhorn's + // own iscsi-installer DaemonSet as an interim fallback. + {"longhorn", "https://charts.longhorn.io", "longhorn", "longhorn-system"}, } // releaseCoord locates an installed component release for Get/Delete/prune. diff --git a/plugins/k8s/internal/provider/platform_test.go b/plugins/k8s/internal/provider/platform_test.go index ee17a73..c7622c1 100644 --- a/plugins/k8s/internal/provider/platform_test.go +++ b/plugins/k8s/internal/provider/platform_test.go @@ -71,6 +71,33 @@ func TestEnabledComponents_NfsProvisioner(t *testing.T) { } } +func TestEnabledComponents_Longhorn(t *testing.T) { + comps := enabledComponents(map[string]any{ + "longhorn": map[string]any{ + "enabled": true, + "values": map[string]any{"defaultSettings": map[string]any{"defaultReplicaCount": 2}}, + }, + }) + if len(comps) != 1 { + t.Fatalf("enabled = %d, want 1 (longhorn)", len(comps)) + } + c := comps[0] + if c.comp.name != "longhorn" { + t.Errorf("component = %q", c.comp.name) + } + if c.chart.Repo != "https://charts.longhorn.io" || c.chart.Name != "longhorn" { + t.Errorf("chart defaults wrong: %+v", c.chart) + } + if c.opts.namespace != "longhorn-system" { + t.Errorf("default namespace = %q, want longhorn-system", c.opts.namespace) + } + // User values (e.g. replica count) thread through to the chart. + ds, _ := c.values["defaultSettings"].(map[string]any) + if ds["defaultReplicaCount"] != 2 { + t.Errorf("longhorn values not threaded: %+v", c.values) + } +} + func TestEnabledComponents_NvidiaDevicePlugin(t *testing.T) { comps := enabledComponents(map[string]any{ "nvidiaDevicePlugin": map[string]any{"enabled": true}, diff --git a/plugins/k8s/internal/provider/schema.go b/plugins/k8s/internal/provider/schema.go index a915ebd..f31739b 100644 --- a/plugins/k8s/internal/provider/schema.go +++ b/plugins/k8s/internal/provider/schema.go @@ -112,6 +112,11 @@ const platformSchema = ` // nfsProvisioner gives a dynamic NFS-backed StorageClass for persistent // volumes (e.g. a Synology share). Set values.nfs.server + values.nfs.path. nfsProvisioner?: {...} + // longhorn is replicated block storage (RWO volumes with node-to-node + // replication + snapshots). Every node needs open-iscsi — install it via + // a k3s worker pool's nodePrep.packages, or Longhorn's iscsi-installer + // DaemonSet as a fallback. + longhorn?: {...} } ... }