|
| 1 | +# Persys Compute Platform Extension Design Spec |
| 2 | + |
| 3 | +Status: Draft |
| 4 | +Owner: Compute + Scheduler teams |
| 5 | +Last Updated: 2026-02-23 |
| 6 | + |
| 7 | +## 1. Problem Statement |
| 8 | + |
| 9 | +We need four production-critical capabilities: |
| 10 | + |
| 11 | +1. A real storage layer that can provision and attach volumes from NFS/Ceph (not only host bind paths). |
| 12 | +2. Dynamic cloud-init injection from user input (current behavior still defaults to mostly static seed generation). |
| 13 | +3. Separation of storage/network concerns from compute runtime logic. |
| 14 | +4. Per-workload (container/VM) utilization telemetry so scheduling, automation, and intelligence can reason about performance. |
| 15 | + |
| 16 | +## 2. Current-State Findings (from code scan) |
| 17 | + |
| 18 | +- `compute-agent/internal/storage/manager.go` provides an in-memory pool/allocation manager only; it is not integrated into runtime Create/Start/Delete flows. |
| 19 | +- `compute-agent/internal/runtime/docker.go` mounts only direct host bind paths (`VolumeMount.host_path`) and has no managed volume abstraction. |
| 20 | +- `compute-agent/internal/runtime/vm.go` generates cloud-init ISO but writes static fallback `meta-data`; `network-config`/`vendor-data` are not seeded into ISO files. |
| 21 | +- `compute-agent/internal/runtime/runtime.go` has a runtime interface only; storage/network provider abstractions are missing. |
| 22 | +- `compute-agent/internal/resources/monitor.go` and `compute-agent/internal/metrics/metrics.go` expose node-level metrics but not workload-level CPU/memory/io/network usage. |
| 23 | +- Scheduler/gateway/ctl mostly pass workload specs through, but control-plane contracts do not yet model managed volume lifecycle or workload telemetry envelopes. |
| 24 | + |
| 25 | +## 3. Goals |
| 26 | + |
| 27 | +- Introduce pluggable volume provisioning/attachment with NFS and Ceph first-class support. |
| 28 | +- Ensure VM cloud-init payload from users is injected faithfully (user-data/meta-data/network-config/vendor-data). |
| 29 | +- Decouple runtime implementations from storage/network implementations with explicit interfaces. |
| 30 | +- Publish workload utilization in agent metrics + scheduler-visible status so users can explain slow workloads. |
| 31 | + |
| 32 | +## 4. Non-Goals (for this phase) |
| 33 | + |
| 34 | +- Full Kubernetes CSI/CNI compatibility. |
| 35 | +- Multi-tenant quota/billing engine. |
| 36 | +- Long-term metrics storage in this same milestone (we expose and stream first). |
| 37 | + |
| 38 | +## 5. Target Architecture |
| 39 | + |
| 40 | +### 5.1 Compute-Agent Platform Layer |
| 41 | + |
| 42 | +Add `compute-agent/internal/platform/`: |
| 43 | + |
| 44 | +- `storage.go`: `StorageProvider`, `VolumeManager`, `VolumeAttachment`. |
| 45 | +- `network.go`: `NetworkProvider`, `NetworkAttachment`. |
| 46 | +- `types.go`: shared structs (`VolumeSpec`, `VolumeHandle`, `WorkloadNetSpec`). |
| 47 | + |
| 48 | +Runtimes (`docker`, `compose`, `vm`) consume these interfaces instead of raw host paths. |
| 49 | + |
| 50 | +### 5.2 Managed Volume Model |
| 51 | + |
| 52 | +Add workload spec support for managed volumes: |
| 53 | + |
| 54 | +- `name`, `driver` (`local|nfs|ceph-rbd`), `size_gb`, `access_mode`, `fs_type`, `mount_path`, `read_only`, `retain_policy`. |
| 55 | + |
| 56 | +Lifecycle: |
| 57 | + |
| 58 | +1. Resolve/provision volume in provider. |
| 59 | +2. Attach/stage for workload. |
| 60 | +3. Runtime mounts staged target. |
| 61 | +4. Detach on stop/delete. |
| 62 | +5. Honor retain/delete policy. |
| 63 | + |
| 64 | +### 5.3 Cloud-Init Injection Model |
| 65 | + |
| 66 | +For VM workloads: |
| 67 | + |
| 68 | +- Accept and persist all cloud-init fields from API. |
| 69 | +- Build NoCloud seed with files: |
| 70 | + - `user-data` |
| 71 | + - `meta-data` |
| 72 | + - `network-config` (optional) |
| 73 | + - `vendor-data` (optional) |
| 74 | +- Keep deterministic seed path and checksum in workload status metadata. |
| 75 | + |
| 76 | +### 5.4 Utilization Telemetry Model |
| 77 | + |
| 78 | +Add workload-level usage snapshot type: |
| 79 | + |
| 80 | +- `workload_id`, `type`, `cpu_percent`, `memory_bytes`, `disk_read_bytes`, `disk_write_bytes`, `net_rx_bytes`, `net_tx_bytes`, `collected_at`, `source`. |
| 81 | + |
| 82 | +Sources: |
| 83 | + |
| 84 | +- Containers/compose: Docker stats API. |
| 85 | +- VMs: libvirt domain stats (CPU time, memory stats, interface/block stats where available). |
| 86 | + |
| 87 | +## 6. Concrete Implementation Plan |
| 88 | + |
| 89 | +## Phase 0: Contracts and Schema (safe-first) |
| 90 | + |
| 91 | +1. Update API contracts. |
| 92 | + |
| 93 | +- `compute-agent/api/proto/agent.proto` |
| 94 | + - Add managed volume structures and fields to container/vm specs. |
| 95 | + - Add optional workload telemetry response shape (or metadata envelope) for status/list. |
| 96 | +- `persys-scheduler/api/proto/control.proto` |
| 97 | + - Mirror managed volume spec. |
| 98 | + - Extend `WorkloadStatus`/`WorkloadView` to include structured reason + optional usage snapshot. |
| 99 | + - Extend heartbeat with repeated workload usage snapshots. |
| 100 | +- Regenerate pb code in `compute-agent/pkg/api/v1`, `compute-agent/pkg/control/v1`, `persys-scheduler/internal/controlv1`, `persys-gateway/internal/controlv1`, `persysctl/internal/controlv1`. |
| 101 | + |
| 102 | +2. Extend models. |
| 103 | + |
| 104 | +- `compute-agent/pkg/models/workload.go`: add managed volume and usage structs. |
| 105 | +- `persys-scheduler/internal/models/models.go`: add managed volume + workload usage fields. |
| 106 | +- `persysctl/internal/models/models.go`: expose new fields for CLI output. |
| 107 | + |
| 108 | +Acceptance: |
| 109 | + |
| 110 | +- Backward-compatible defaults preserve old specs. |
| 111 | +- Existing apply/list/get calls still work. |
| 112 | + |
| 113 | +## Phase 1: Storage Provider Integration (NFS + Ceph) |
| 114 | + |
| 115 | +1. Implement provider interfaces. |
| 116 | + |
| 117 | +- New: |
| 118 | + - `compute-agent/internal/platform/storage.go` |
| 119 | + - `compute-agent/internal/storage/providers/local_provider.go` |
| 120 | + - `compute-agent/internal/storage/providers/nfs_provider.go` |
| 121 | + - `compute-agent/internal/storage/providers/ceph_rbd_provider.go` |
| 122 | + |
| 123 | +2. Persist volume state. |
| 124 | + |
| 125 | +- Extend state store with `volumes` and `attachments` buckets. |
| 126 | +- Files: |
| 127 | + - `compute-agent/internal/state/store.go` |
| 128 | + - `compute-agent/internal/state/bolt_*.go` (new file split recommended) |
| 129 | + |
| 130 | +3. Integrate with workload manager lifecycle. |
| 131 | + |
| 132 | +- `compute-agent/internal/workload/manager.go` |
| 133 | + - Pre-create/attach managed volumes before runtime `Create`. |
| 134 | + - Detach/cleanup on delete based on retain policy. |
| 135 | + - Persist explicit failure reason (`STORAGE_ATTACH_FAILED`, `STORAGE_PROVISION_FAILED`). |
| 136 | + |
| 137 | +4. Runtime wiring. |
| 138 | + |
| 139 | +- `compute-agent/internal/runtime/docker.go` |
| 140 | + - Convert managed volume attachments into runtime mounts. |
| 141 | +- `compute-agent/internal/runtime/vm.go` |
| 142 | + - Attach Ceph/NFS-backed disks through libvirt disk source definitions. |
| 143 | + |
| 144 | +5. Scheduler capability-awareness. |
| 145 | + |
| 146 | +- `compute-agent/internal/control/client.go` heartbeat/register: advertise storage driver capabilities. |
| 147 | +- `persys-scheduler/internal/scheduler/scheduler.go`: only place workloads on nodes supporting requested storage driver. |
| 148 | + |
| 149 | +Acceptance: |
| 150 | + |
| 151 | +- Container can request NFS/Ceph volume and start with mounted volume. |
| 152 | +- VM can boot with Ceph/NFS-backed disk where requested. |
| 153 | +- Delete honors retain/delete policy. |
| 154 | + |
| 155 | +## Phase 2: Dynamic Cloud-Init End-to-End |
| 156 | + |
| 157 | +1. Preserve full cloud-init fields through control path. |
| 158 | + |
| 159 | +- `persysctl/cmd/scheduler.go` and scheduler conversion helpers keep `user_data`, `meta_data`, `network_config`, `vendor_data` unchanged. |
| 160 | +- Ensure no lossy conversion via reduced legacy control VM schema. |
| 161 | + |
| 162 | +2. Cloud-init ISO builder update. |
| 163 | + |
| 164 | +- `compute-agent/internal/runtime/vm.go` |
| 165 | + - `createCloudInitISO`: write `meta-data` from user payload when provided. |
| 166 | + - Write `network-config` and `vendor-data` files if provided. |
| 167 | + - Include payload checksum in status metadata. |
| 168 | + |
| 169 | +3. Validation and safety. |
| 170 | + |
| 171 | +- Reject oversized/invalid cloud-init payload with explicit user-visible errors. |
| 172 | +- Redact sensitive cloud-init fields from logs while retaining hash and size. |
| 173 | + |
| 174 | +Acceptance: |
| 175 | + |
| 176 | +- User-provided cloud-init is faithfully applied. |
| 177 | +- Status shows `vm.cloud_init_seed_checksum`, `vm.cloud_init_seed_path`. |
| 178 | + |
| 179 | +## Phase 3: Storage/Network Abstraction from Runtime |
| 180 | + |
| 181 | +1. Introduce dependency-injected runtime context. |
| 182 | + |
| 183 | +- `compute-agent/internal/runtime/runtime.go` |
| 184 | + - Add optional runtime dependencies struct (`StorageProvider`, `NetworkProvider`). |
| 185 | + |
| 186 | +2. Network provider implementation. |
| 187 | + |
| 188 | +- New `compute-agent/internal/network/providers/` with initial: |
| 189 | + - Docker network provider wrapper. |
| 190 | + - Libvirt network resolver wrapper. |
| 191 | + |
| 192 | +3. Refactor runtimes. |
| 193 | + |
| 194 | +- `docker.go`, `compose.go`, `vm.go` use provider interfaces, not direct ad-hoc host/network assumptions. |
| 195 | + |
| 196 | +4. Bootstrap wiring. |
| 197 | + |
| 198 | +- `compute-agent/cmd/agent/main.go`: instantiate provider registry and inject into runtime constructors. |
| 199 | +- `compute-agent/internal/config/config.go`: add provider-specific configuration (NFS mount options, Ceph pool/user/keyring, default network policies). |
| 200 | + |
| 201 | +Acceptance: |
| 202 | + |
| 203 | +- Runtime packages compile/test against mock providers. |
| 204 | +- Storage/network behavior can be tested without live Docker/libvirt by mocking providers. |
| 205 | + |
| 206 | +## Phase 4: Workload Utilization Telemetry |
| 207 | + |
| 208 | +1. Agent collectors. |
| 209 | + |
| 210 | +- New `compute-agent/internal/telemetry/workload_usage_collector.go`. |
| 211 | +- Poll every `N` seconds and cache latest usage per workload. |
| 212 | + |
| 213 | +2. Metrics exposure. |
| 214 | + |
| 215 | +- Extend `compute-agent/internal/metrics/metrics.go` with labeled gauges/counters: |
| 216 | + - `persys_agent_workload_cpu_percent{workload_id,type}` |
| 217 | + - `persys_agent_workload_memory_bytes{workload_id,type}` |
| 218 | + - `persys_agent_workload_disk_read_bytes_total{workload_id,type}` |
| 219 | + - `persys_agent_workload_disk_write_bytes_total{workload_id,type}` |
| 220 | + - `persys_agent_workload_network_rx_bytes_total{workload_id,type}` |
| 221 | + - `persys_agent_workload_network_tx_bytes_total{workload_id,type}` |
| 222 | + |
| 223 | +3. Status and heartbeat propagation. |
| 224 | + |
| 225 | +- Agent `GetWorkloadStatus/ListWorkloads`: include latest usage snapshot in metadata/structured field. |
| 226 | +- `compute-agent/internal/control/client.go`: include per-workload usage in heartbeat. |
| 227 | +- Scheduler stores latest usage and surfaces it in: |
| 228 | + - `persys-scheduler/internal/grpcapi/service.go` (`WorkloadView`) |
| 229 | + - Gateway pass-through (`persys-gateway/controllers/scheduler.controller.go` + generated pb). |
| 230 | + |
| 231 | +4. User-facing diagnostics. |
| 232 | + |
| 233 | +- `persysctl` output (`workload list/get`) includes utilization and last sample timestamp. |
| 234 | +- For failed/pending/frozen workloads, show reason codes and runtime reason text from metadata. |
| 235 | + |
| 236 | +Acceptance: |
| 237 | + |
| 238 | +- `workload list/get` shows recent per-workload CPU/memory and at least one IO/network signal. |
| 239 | +- Scheduler can filter/report “high CPU” or “memory pressure” candidate workloads in future automation. |
| 240 | + |
| 241 | +## 7. Cross-Cutting Reliability Changes |
| 242 | + |
| 243 | +- Add reason-code taxonomy (shared enum or canonical string set): |
| 244 | + - `STORAGE_PROVISION_FAILED`, `STORAGE_ATTACH_FAILED`, `NETWORK_ATTACH_FAILED`, `CLOUD_INIT_INVALID`, `VM_PAUSED_IO_ERROR`, `WORKLOAD_RESOURCE_STARVATION`. |
| 245 | +- Ensure each reconcile failure writes: |
| 246 | + - machine-readable reason code, |
| 247 | + - human-readable message, |
| 248 | + - last transition time, |
| 249 | + - next retry time if retryable. |
| 250 | + |
| 251 | +## 8. Rollout Strategy |
| 252 | + |
| 253 | +1. Ship contracts first behind feature gates: |
| 254 | + |
| 255 | +- `PERSYS_FEATURE_MANAGED_VOLUMES` |
| 256 | +- `PERSYS_FEATURE_DYNAMIC_CLOUD_INIT` |
| 257 | +- `PERSYS_FEATURE_WORKLOAD_TELEMETRY` |
| 258 | + |
| 259 | +2. Enable in canary cluster order: |
| 260 | + |
| 261 | +- telemetry -> cloud-init -> storage provider path. |
| 262 | + |
| 263 | +3. Keep fallback paths: |
| 264 | + |
| 265 | +- old host bind volume behavior remains valid. |
| 266 | +- old cloud-init single string remains valid. |
| 267 | + |
| 268 | +## 9. Test Plan |
| 269 | + |
| 270 | +- Unit tests: |
| 271 | + - provider allocation/attach/detach behavior. |
| 272 | + - cloud-init ISO generation with golden fixtures. |
| 273 | + - runtime + provider integration via mocks. |
| 274 | +- Integration tests: |
| 275 | + - NFS volume attach to container. |
| 276 | + - Ceph RBD attach to VM. |
| 277 | + - cloud-init network-config applied on VM boot. |
| 278 | + - telemetry visible in scheduler `GetWorkload`. |
| 279 | +- Chaos tests: |
| 280 | + - NFS/Ceph outage during attach. |
| 281 | + - libvirt transient failures during stats collection. |
| 282 | + |
| 283 | +## 10. Milestone Breakdown (Execution Order) |
| 284 | + |
| 285 | +1. Milestone A (1-2 weeks): proto/model updates + compatibility + CLI/gateway regeneration. |
| 286 | +2. Milestone B (2-3 weeks): storage provider framework + NFS driver + container integration. |
| 287 | +3. Milestone C (2-3 weeks): Ceph RBD + VM disk attach path + state persistence. |
| 288 | +4. Milestone D (1-2 weeks): dynamic cloud-init full payload support. |
| 289 | +5. Milestone E (2 weeks): workload telemetry collection + exposure end-to-end. |
| 290 | +6. Milestone F (1 week): hardening, migration docs, runbooks. |
| 291 | + |
| 292 | +## 11. Open Decisions |
| 293 | + |
| 294 | +- Ceph auth distribution mechanism (static keyring vs Vault-injected credentials). |
| 295 | +- Retain policy defaults for managed volumes (`Delete` vs `Retain`). |
| 296 | +- Whether per-workload usage history is kept in etcd or only current snapshot in control plane. |
| 297 | + |
| 298 | +## 12. Definition of Done |
| 299 | + |
| 300 | +- Managed volumes (NFS/Ceph) can be provisioned/attached without host-path hardcoding. |
| 301 | +- Cloud-init user payload is injected as-is and traceable via checksum/metadata. |
| 302 | +- Runtime packages depend on provider abstractions, not direct storage/network assumptions. |
| 303 | +- Users can inspect per-workload utilization and precise failure reasons from scheduler/gateway/`persysctl`. |
0 commit comments