Kills the "CRD owns a list of device ids" smell. Deployment CR now
carries a standard K8s LabelSelector; Device is a first-class cluster-
scoped CR (like Node). Matching, desired-state KV writes, and status
aggregation all run off selector evaluation against the Device cache
— no list of device ids anywhere in the CRD spec.
Cross-resource model:
- Agent publishes DeviceInfo (with labels) to NATS `device-info` KV.
- device_reconciler watches that bucket → server-side-applies a
cluster-scoped Device CR with metadata.labels + spec.inventory.
- Deployment controller is now just validation + finalizer cleanup.
- fleet_aggregator watches Deployment CRs + Device CRs + device-state
KV, maintains in-memory selector → target device sets, writes/deletes
`desired-state.<device>.<deployment>` KV on match changes, patches
`.status.aggregate` at 1 Hz with matchedDeviceCount + phase counters.
Applied CRD shape verified on a live k3d cluster:
kubectl get crd deployments.iot.nationtech.io -o json
.spec.versions[0].schema.openAPIV3Schema.properties.spec
→ rollout / score / targetSelector (matchLabels + matchExpressions)
.spec.versions[0].schema.openAPIV3Schema.properties.status.aggregate
→ matchedDeviceCount / succeeded / failed / pending / lastError
kubectl get crd devices.iot.nationtech.io -o json
.spec.scope = "Cluster"
.spec.versions[0].schema.openAPIV3Schema.properties.spec
→ inventory (nullable, camelCased fields)
Load-test run: DEVICES=20 GROUP_SIZES=10,5,5 DURATION=20
all 3 CRs hit expected matched=N / succeeded+failed+pending=N.
Other changes:
- k8s-openapi gets the `schemars` feature so LabelSelector derives JsonSchema.
- InventorySnapshot uses `#[serde(rename_all = "camelCase")]` for consistency with the rest of the CRD schema.
- agent publishes `device-id=<id>` as a default label so the
example_iot_apply_deployment `--target-device <id>` shorthand
works out-of-the-box (implemented as `--selector device-id=<id>`).
- example_iot_apply_deployment gains `--selector key=value` repeatable flag.
- load-test.sh explore banner exposes Device CR commands + new
matchedDeviceCount column.
23 lines
889 B
TOML
23 lines
889 B
TOML
[package]
|
|
name = "harmony-reconciler-contracts"
|
|
version = "0.1.0"
|
|
edition = "2024"
|
|
license.workspace = true
|
|
|
|
# Cross-boundary types shared between a harmony operator (central,
|
|
# writing desired state to NATS JetStream KV) and a harmony agent
|
|
# (on-host, watching KV and reconciling). Deliberately lean: pure
|
|
# serde data types, bucket/key constants, small helpers. No tokio,
|
|
# no async-nats, no harmony. The on-device agent build pulls this
|
|
# in alongside a minimal async-nats client; the operator pulls it
|
|
# alongside kube-rs; harmony itself treats it as just another
|
|
# module. None of those consumers should pay for the others' deps.
|
|
|
|
[dependencies]
|
|
chrono = { workspace = true, features = ["serde"] }
|
|
harmony_types = { path = "../harmony_types" }
|
|
schemars = "0.8.22"
|
|
serde = { workspace = true, features = ["derive"] }
|
|
serde_json = { workspace = true }
|
|
thiserror = { workspace = true }
|