Skip to content

Commit f4f2dd2

Browse files
kvapsclaude
andcommitted
feat: autoplacer + Resource POST/DELETE; sync PLAN.md checklist
This closes the last definition-side gap in the CSI MVP slice. With this slice, linstor-csi's CreateVolume flow round-trips end-to-end at the REST layer: 1. POST /v1/resource-groups/{rg}/spawn → RD + VDs (existing) 2. POST /v1/resource-definitions/{rd}/autoplace → place_count Resources 3. … (Phase 3: satellite reconciles those Resources) The Phase 2.5 autoplacer: - Merges request select_filter on top of the parent RG's stored filter, request fields winning. Empty request inherits everything. - Filters StoragePool list by kind (skips DISKLESS), name, list, and node-name list. - Skips nodes that already host a replica of this RD; creates Resources up to PlaceCount; returns 409 if there aren't enough candidates. - Phase 3's autoplacer will weigh free capacity, traits, anti-affinity; this stub is good enough to drive linstor-csi against the in-cluster oracle today. Also adds explicit single-resource placement: - POST /v1/resource-definitions/{rd}/resources (linstor-csi diskless toggles) - DELETE /v1/resource-definitions/{rd}/resources/{node} Tests (5 contract): - pick-requested-count, conflict-when-insufficient, missing-RD→404, inherits-RG-filter, resource-create-then-delete round-trip. PLAN.md: tick the boxes that were already done but I had not synced — Phase 1 closed (apiconsts via golinstor, linstor-common submodule), Phase 2 definition side closed; Phase 2.5 autoplacer/resource POST done; reconcilers + Phase 3 satellite still ⬜. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 7d360c1 commit f4f2dd2

6 files changed

Lines changed: 562 additions & 39 deletions

File tree

PLAN.md

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,39 @@ high-bandwidth check-ins with the user.
1515

1616
## Current status
1717

18-
- **Phase**: 2 well underway. CRD-backed store live, four resource families wired end-to-end.
19-
- **Implemented (REST + Store + tests, all both InMemory and CRD/envtest backends, golinstor compatibility verified)**:
20-
- `/v1/controller/version`, `/v1/healthz`
21-
- `/v1/nodes` — full CRUD
22-
- `/v1/view/storage-pools`, `/v1/nodes/{node}/storage-pools[/{pool}]` — read
23-
- `/v1/resource-groups` — full CRUD
24-
- `/v1/resource-definitions` — full CRUD (POST unwraps the upstream `{resource_definition: {...}}` envelope)
25-
- `/v1/view/resources` — read
26-
- **CRDs (kubebuilder-scaffolded, NodeSpec/StatuS filled with LINSTOR-shaped fields)**:
27-
- `Node`, `StoragePool`, `ResourceGroup`, `ResourceDefinition`, `Resource` (composite-keyed via `<a>.<b>` metadata.name + label index)
28-
- **Stores**: `pkg/store` (InMemory) and `pkg/store/k8s` (controller-runtime client), both behind the same `store.Store` interface and exercised by the same `pkg/store/storetest` shared suite — behavioural drift fails the same subtest immediately.
29-
- **Tests**: 150+ unit + contract; controller-runtime envtest harness skips cleanly when `KUBEBUILDER_ASSETS` is missing, so plain `go test ./...` is green without `make setup-envtest`.
30-
- **Lint**: `golangci-lint run ./...` zero issues. Auto-lint hook (`golangci-lint@claude-code-companions`) catches every Go-file edit.
18+
- **Phase**: 2 — definition side done, reconcilers + autoplacer next.
19+
- **CRDs (6, kubebuilder-scaffolded, LINSTOR-shaped fields)**:
20+
`Node`, `StoragePool`, `ResourceGroup`, `ResourceDefinition`, `Resource`,
21+
`Snapshot`. VolumeDefinitions live inline on `ResourceDefinition.Spec`.
22+
- **Stores**: `pkg/store` (InMemory) and `pkg/store/k8s` (controller-runtime
23+
client), both behind the same `store.Store` interface and exercised by the
24+
same `pkg/store/storetest` shared suite — behavioural drift fails the same
25+
subtest immediately. KeyValueStore is ConfigMap-backed (with `/``__slash__`
26+
key encoding because ConfigMap data keys can't contain '/').
27+
- **Endpoints live (~25 of the CSI MVP slice)**:
28+
`/v1/controller/version`, `/v1/healthz`, `/v1/nodes`,
29+
`/v1/view/storage-pools`, `/v1/nodes/{n}/storage-pools[/{p}]`,
30+
`/v1/resource-groups`, `/v1/resource-groups/{rg}/spawn`,
31+
`/v1/resource-definitions`, `/v1/resource-definitions/{rd}/volume-definitions`,
32+
`/v1/resource-definitions/{rd}/snapshots`, `/v1/view/resources`,
33+
`/v1/view/snapshots`, `/v1/key-value-store`.
34+
- **Tests**: 200+ unit + contract; envtest harness skips cleanly when
35+
`KUBEBUILDER_ASSETS` is missing.
36+
- **Lint**: `golangci-lint run ./...` zero issues. Auto-lint hook on every
37+
Go-file edit.
3138
- **Blocker**: none.
32-
- **Next concrete steps**:
33-
1. `/v1/resource-definitions/{rd}/volume-definitions` (CRUD) — needed before linstor-csi can size a PVC.
34-
2. `/v1/resource-groups/{rg}/spawn` — the CSI provisioner calls this on every `CreateVolume`. Needs an `Autoplacer` stub: read RG select-filter, pick `place_count` storage pools, create matching `Resource`s.
35-
3. `/v1/key-value-store` — linstor-csi keeps its own per-volume bookkeeping here.
36-
4. `/v1/resource-definitions/{rd}/snapshots` + `/v1/view/snapshots` — Snapshot CRD; mostly mirrors Resource shape.
37-
5. Reconcilers (no-op for now → satellite-driven in Phase 3) for `Node`, `StoragePool`, `Resource`. Wire `mgr.Add` for them in `cmd/main.go`.
39+
- **Next concrete steps** (Phase 2.5):
40+
1. `POST /v1/resource-definitions/{rd}/autoplace` — autoplacer that reads
41+
the parent RG's `select_filter`, finds matching nodes/storage-pools, and
42+
creates `Resource` objects up to `place_count`.
43+
2. `POST/DELETE /v1/resource-definitions/{rd}/resources[/{node}]` — explicit
44+
resource placement (linstor-csi uses this for diskless toggles).
45+
3. Reconcilers (no-op for Phase 2.5; Phase 3 fills them) for `Node`,
46+
`StoragePool`, `ResourceDefinition`, `Resource`, `Snapshot`. Wire them
47+
in `cmd/main.go`.
48+
4. Phase 3 begin: gRPC contract between controller and `cmd/satellite`
49+
binary; satellite's first job is `Resource` reconciliation (DRBD .res
50+
generation + drbdadm + LVM/ZFS provisioning).
3851

3952
---
4053

@@ -156,30 +169,49 @@ Full scope list lives in `docs/csi-api-surface.md` (to be created in Phase 1).
156169

157170
**Exit met**: full happy-path PVC test passes against upstream Java stack, on parallelizable stand.
158171

159-
### Phase 1 — Skeleton + contracts
172+
### Phase 1 — Skeleton + contracts (done)
160173

161174
- [x] New module via `kubebuilder init`; controller-runtime manager + `pkg/rest` Runnable
162175
- [x] `/v1/controller/version` returns a credible response (`pkg/version` constants pin LINSTOR contract version)
163176
- [x] `golinstor.Client.Controller.GetVersion()` against our server returns no error (`pkg/rest/server_test.go`)
164177
- [x] Full-branch tests for the version endpoint and the Runnable lifecycle (8 cases)
165178
- [x] CSI MVP scope frozen in `docs/csi-api-surface.md`
166179
- [x] golangci-lint v2 config + auto-lint hook (`golangci-lint@claude-code-companions`) wired
167-
- [ ] OpenAPI types generated from `linstor-server/docs/rest_v1_openapi.yaml` (oapi-codegen)
168-
- [ ] `apiconsts` ported from golinstor (ApiCallRc codes for error responses)
169-
- [ ] `linstor-common` submodule (properties.json, consts.json, drbdoptions.json)
170-
171-
**Exit**: golinstor can talk to us for `GetVersion`; tests in CI green. **Met.**
172-
Remaining items above land at the start of Phase 2 (codegen wiring is most
173-
useful once we have many types to generate, which Phase 2 introduces).
174-
175-
### Phase 2 — CRDs + reconcile
176-
177-
- [ ] CRDs: `Node`, `StoragePool`, `ResourceGroup`, `ResourceDefinition`, `Resource`, `Volume`, `Snapshot`
178-
- [ ] controller-runtime manager, reconcilers stubbed
179-
- [ ] `Nodes.{GetAll,Get,Create,Modify,Delete}` work end-to-end against CRDs
180-
- [ ] linstor-csi container can register a node against our server
181-
182-
**Exit**: piraeus-operator can create `LinstorSatellite`s and they appear as nodes in our API.
180+
- [x] `linstor-common` submodule (properties.json, consts.json, drbdoptions.json)
181+
- [x] `apiconsts` reused from `github.com/LINBIT/golinstor` — no fork needed
182+
- [ ] OpenAPI types generated from `rest_v1_openapi.yaml` (oapi-codegen) — deferred; types are
183+
hand-written for now, codegen lands when we cover stats/error-reports endpoints
184+
185+
**Exit met.**
186+
187+
### Phase 2 — CRDs + reconcile (REST/store side done; reconcilers pending)
188+
189+
- [x] CRDs: `Node`, `StoragePool`, `ResourceGroup`, `ResourceDefinition`, `Resource`, `Snapshot` (VolumeDefinition is inline in RD spec)
190+
- [x] controller-runtime manager wired in `cmd/main.go`; reconciler stubs scaffolded by kubebuilder
191+
- [x] `Nodes.{GetAll,Get,Create,Modify,Delete}` work end-to-end against CRDs
192+
- [x] `Store` interface with `InMemory` and `k8s` (CRD-backed) implementations, both exercised by the same `pkg/store/storetest` shared suite
193+
- [x] `cmd/main.go --store={k8s|memory}` flag (default `k8s`)
194+
- [x] envtest harness (`make setup-envtest`); `go test ./...` skips cleanly without assets
195+
- [x] All write/read paths the CSI MVP needs land via `golinstor`:
196+
- `/v1/nodes` (CRUD)
197+
- `/v1/view/storage-pools`, `/v1/nodes/{n}/storage-pools[/{p}]`
198+
- `/v1/resource-groups` (CRUD), `/v1/resource-groups/{rg}/spawn`
199+
- `/v1/resource-definitions` (CRUD)
200+
- `/v1/resource-definitions/{rd}/volume-definitions` (CRUD)
201+
- `/v1/resource-definitions/{rd}/snapshots` (CRUD), `/v1/view/snapshots`
202+
- `/v1/view/resources`
203+
- `/v1/key-value-store` (CRUD; ConfigMap-backed)
204+
- [ ] **Reconcilers** for `Node` / `StoragePool` / `ResourceDefinition` /
205+
`Resource` / `Snapshot` — currently no-op stubs; Phase 3 fills them.
206+
- [ ] **Autoplacer**`POST /v1/resource-definitions/{rd}/autoplace`
207+
computes placement and creates `Resource` objects.
208+
- [ ] **Resource POST/DELETE**`/v1/resource-definitions/{rd}/resources[/{node}]`
209+
so linstor-csi can place replicas explicitly.
210+
- [ ] piraeus-operator can create `LinstorSatellite`s and they appear in our API.
211+
212+
**Exit (definition side)**: ✅ — all REST/store paths the CSI MVP touches
213+
land via golinstor; both stores pass the same suite. **Reconcilers + autoplacer
214+
land in Phase 2.5 below before we move to satellite/DRBD work.**
183215

184216
### Phase 3 — Satellite + DRBD lifecycle
185217

docs/csi-api-surface.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,10 @@ schedules, S3 / EBS, SPDK, NVMe-oF, OpenFlex, Exos) MUST return
8686

8787
| Endpoint | Method | Status |
8888
|---|---|---|
89-
| `/v1/resource-definitions/{rd}/resources` | GET / POST ||
90-
| `/v1/resource-definitions/{rd}/resources/{node}` | GET / PUT / DELETE ||
89+
| `/v1/resource-definitions/{rd}/resources` | POST ||
90+
| `/v1/resource-definitions/{rd}/resources` | GET | ⬜ (use /v1/view/resources) |
91+
| `/v1/resource-definitions/{rd}/resources/{node}` | DELETE ||
92+
| `/v1/resource-definitions/{rd}/resources/{node}` | GET / PUT ||
9193
| `/v1/resource-definitions/{rd}/resources/{node}/volumes` | GET ||
9294
| `/v1/resource-definitions/{rd}/resources/{node}/volumes/{vn}` | GET / PUT ||
9395
| `/v1/resource-definitions/{rd}/resources/{node}/migrate-disk/{from}` | PUT ||
@@ -99,7 +101,7 @@ schedules, S3 / EBS, SPDK, NVMe-oF, OpenFlex, Exos) MUST return
99101
| `/v1/resource-definitions/{rd}/resources/{node}/make-available` | POST ||
100102
| `/v1/resource-definitions/{rd}/resources/{node}/activate` | POST ||
101103
| `/v1/resource-definitions/{rd}/resources/{node}/deactivate` | POST ||
102-
| `/v1/resource-definitions/{rd}/autoplace` | POST | |
104+
| `/v1/resource-definitions/{rd}/autoplace` | POST | |
103105

104106
## Resource groups
105107

pkg/api/v1/autoplace.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2026 Cozystack contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
// AutoPlaceRequest is the body upstream LINSTOR (and golinstor) expect on
20+
// `POST /v1/resource-definitions/{rd}/autoplace`. Mirrors golinstor exactly.
21+
type AutoPlaceRequest struct {
22+
DisklessOnRemaining bool `json:"diskless_on_remaining,omitempty"`
23+
SelectFilter AutoSelectFilter `json:"select_filter,omitzero"`
24+
LayerList []string `json:"layer_list,omitempty"`
25+
}
26+
27+
// ResourceCreate is the body upstream LINSTOR uses on
28+
// `POST /v1/resource-definitions/{rd}/resources`. The Resources field is a
29+
// list because callers can place a resource on multiple nodes in one call.
30+
type ResourceCreate struct {
31+
Resource Resource `json:"resource,omitzero"`
32+
LayerList []string `json:"layer_list,omitempty"`
33+
DrbdNodeID *int32 `json:"drbd_node_id,omitempty"`
34+
NetInterface string `json:"net_interface,omitempty"`
35+
}

0 commit comments

Comments
 (0)