Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Demo presentation docs (local prep, not shipped documentation)
docs/DEMO_STRATEGY_WEBEX.md

# Pre-existing inputs (not products of the wiggum loop)
REVERSE.md
specs/
Expand Down
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,9 @@ feeding back into it.
Everything below is self-contained: the instructions live here rather than in
a separate specification.

> **Built with [Specstride](https://specstride.ai).** Not a badge — the method.
> Every spec, plan, task and verification gate in this lab was driven through
> Specstride's spec-driven pipeline: the autonomous tier, the controllers and
> the fabric weren't hand-waved into existence, they were *specified* into
> existence, then proven against the running system. A network that writes
> itself deserves a process that does too — **spec it, stride it, ship it.**

## Demo

Full walkthrough (~6½ min, 1.7x) — the intent tier end to end: three services
Full walkthrough (~6½ min, 6x) — the intent tier end to end: three services
provisioned from plain-language prompts typed into the operator console (a
**vlan**, an **ip-vrf** with its prefix, and a **mac-vrf** stretched across
both leaves), each one confirmed through the mapper and allocator agents,
Expand All @@ -47,7 +40,7 @@ reported deployed, and then proven in the terminal with `kubectl` (the
the SONiC leaf itself (CONFIG_DB, the bridge, the tenant VRF's L3VNI and
Type-5 EVPN route, the VNI tunnel map on both leaves).

[![Three services provisioned from plain-language prompts and proven on the fabric](docs/media/agentic-netops-demo-thumb.jpg)](docs/media/agentic-netops-demo.mp4)
https://github.com/user-attachments/assets/09b5c615-8dc5-4e3e-8937-f6eb16ea770f

Every "deployed" claim in the recording was re-verified from `kubectl` JSON;
the collected evidence is in
Expand Down
38 changes: 35 additions & 3 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ the same intent is applied twice. Those are the sections that matter here.

## The loop

> Preparing to present this lab? See [docs/DEMO_STRATEGY_WEBEX.md](docs/DEMO_STRATEGY_WEBEX.md)
> (run of show, building-blocks narrative, lessons learned) and
> [docs/DEMO_TUTORIAL.md](docs/DEMO_TUTORIAL.md) (hands-on steps for SDC/Kubenet/KUID/SRv6,
> services, and UUID/identifier retrieval).

```
plain language ┌──────────────┐
"connect tenant ──────►│ supervisor │ classify: provisionable?
Expand Down Expand Up @@ -226,9 +231,36 @@ Real, reproduced, documented rather than hidden:

- The former SONiC ASan/L2-VNI and EVPN Type-5 gaps (D-A2/D-A3) are resolved
on the pinned `sonic-vs-gnmi:202505-v1` image and their waivers are retired.
- **SRv6 conformance needs the `sonic-vm` profile**, which requires an
operator-built vrnetlab image *and* a `lab/profiles/sonic-vm/bootstrap/` directory
that does not exist yet.
- **SRv6 conformance runs on the `sonic-vm` profile**, which is now fully
wired AND qualified: `lab/profiles/sonic-vm/bootstrap/` (VM-aware,
SSH-driven gNMI/TLS provisioning) and `lab/topology.sonic-vm.clab.yml`
exist, `scripts/build-sonic-vm.sh` builds the vrnetlab image, and the full
qualification gate (`scripts/lib/qualify.sh`: Capabilities/Get/Set/
Subscribe, sonic-srv6, persistence, EVPN Type-2/3/5, SRv6 underlay/End/
End.DT46/decap/counters, YANG paths) passes end-to-end against
`local/sonic-vm:202505`. It still requires the **operator** to build the
`local/sonic-vm` image
(`scripts/build-sonic-vm.sh --image-url <sonic-vs.img.gz> --update-pin`),
because no public registry serves it, plus KVM/nested virtualization on the
host. Until that build exists on your host, `--profile sonic-vm` fails at
deploy with a missing-image error (the documented gate-fail path).
sonic-vm-profile notes (all verified on the pinned image):
- gNMI TLS is driven by the **`GNMI` CONFIG_DB table** (`GNMI|gnmi` +
`GNMI|certs`), not the legacy `TELEMETRY` table — `gnmi-native.sh`
renders `telemetry_vars.j2` against `GNMI`, and an undefined table makes
it fall back to `--noTLS`. The bootstrap applies both tables.
- The vrnetlab launcher resets the guest admin password to containerlab's
`PASSWORD` env (default `admin`); the guest is reachable over SSH on the
**launcher's own mgmt IP**, not 127.0.0.1 (loopback→slirp stalls).
- The image default config gives every node `Loopback0=10.1.0.1/32` and
bgpd AS 65100/route-id 10.1.0.1 — test topologies must not use 10.1.0.1
as a peer address (martian-dropped) and must set distinct router-ids.
- The guest kernel (SONiC Debian 6.1) has **no kernel seg6 lwtunnel**
support: the kernel-side `encap seg6` dataplane witnesses are
auto-skipped on this profile; SRv6 control-plane state is still asserted
over gNMI. Kernel seg6 witnesses run on the `sonic-vs` profile.
- Persistence on `sonic-vm` is verified with a guest `config reload`
(re-reads `/etc/sonic/config_db.json`) rather than a container restart.
- **Two pinned images have no local build step** (`grafana/flow-plugin`,
`ghcr.io/agentic-netops/topology-generator`); `deployment/ui` ends in `ImagePullBackOff`.
- **`docs/INTENT_TIER_OPS_READINESS.md` holds resource figures that were never
Expand Down
280 changes: 280 additions & 0 deletions docs/DEMO_TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
# agentic-netops demo tutorial — building blocks, services, and identifiers

A step-by-step companion to [TUTORIAL.md](../TUTORIAL.md) focused on the four
building blocks — **SDC (sdcio)**, **Kubenet**, **KUID**, **SRv6** — the services
you can provision live, and how to retrieve object UUIDs and allocated identifiers
from Kubernetes. For the call-planning view see
[DEMO_STRATEGY_WEBEX.md](DEMO_STRATEGY_WEBEX.md).

> Naming note: "kuuid" in the project is **KUID** (`id.kuid.dev`), the
> index/claim allocator, deployed alongside **Kubenet** (`network.kubenet.dev`).

---

## 0. Bring the lab up

```bash
cd /root/agentic-netops
cp .env.example .env # set LLM_MODEL — without it the tier deploys but cannot reason
./scripts/provision.sh --profile sonic-vs --cluster-name agentic-netops --with-intent-tier
export CTX='--context kind-agentic-netops'
```

> **Paste-safety tip:** every command below is a single line on purpose. When
> pasting multi-line commands from notes or slides, a broken `\` continuation
> makes kubectl read the column spec as an object *name* — the tell-tale error is
> `Error from server (NotFound): ... "custom-columns=..." not found`. If you see
> it, re-run the line as one; also confirm `echo $CTX` prints
> `--context kind-agentic-netops`.

Confirm the three stacks are installed:

```bash
kubectl $CTX get crd | grep -E 'kubenet|kuid|sdcio'
# networks.network.kubenet.dev srv6services.agentic-netops.io
# ipindexes.id.kuid.dev asnindexes.id.kuid.dev vniindexes.id.kuid.dev claims.id.kuid.dev
# configs.sdc.sdcio.dev targets.sdc.sdcio.dev schemas.sdc.sdcio.dev ...
```

Fabric sanity: two spines, two leaves, four clients in containerlab
(`docker ps | grep clab-agentic-netops`), underlay established:

```bash
docker exec clab-agentic-netops-fabric-leaf01 vtysh -c 'show bgp summary'
```

---

## 1. Building block: SDC / sdcio (model-driven config plane)

SDC runs in-cluster (Schema server, Config server, Data server, Cache; manifests in
`deploy/sdc/`). It is the only path to the devices: controllers write Config
resources, SDC renders them against the SONiC YANG schema and applies over gNMI.

```bash
# the fabric nodes as SDC targets, and their sync state
kubectl $CTX get targets.sdc.sdcio.dev -A

# desired config per service, held declaratively
kubectl $CTX get configs.sdc.sdcio.dev -A

# schemas loaded for SONiC
kubectl $CTX get schemas.sdc.sdcio.dev -A
```

What to say: agents never open a device session — they only write Kubernetes
resources. SDC is where "declarative" becomes bytes on the box, and because every
apply is a reconcile of desired state, re-applying unchanged intent produces **zero**
writes:

```bash
./tests/integration/idempotence.sh # asserts no SDC spec writes, no gNMI Sets
```

---

## 2. Building block: Kubenet (service intent, `network.kubenet.dev`)

Kubenet provides the topology and network-service model. This repo deploys a
`Topology` (the 2×2 Clos), and adds the `Network` CR (service types: vlan,
mac-vrf, ip-vrf, acl) plus the `SRv6Service` CR.

```bash
kubectl $CTX -n kubenet-system get topology
kubectl $CTX -n agentic-netops-intent get networks.network.kubenet.dev

# watch desired state and the controller's verdict, live
kubectl $CTX -n agentic-netops-intent get networks.network.kubenet.dev -o 'custom-columns=NAME:.metadata.name,TYPE:.metadata.annotations.agentic-netops\.io/service-type,READY:.status.conditions[?(@.type=="Ready")].status' -w
```

`Ready=True` is written only after every rendered operation applied **and** every
per-node verification passed — and it is re-verified every five minutes.

---

## 3. Building block: KUID (identifier allocation, `id.kuid.dev`)

KUID models scarce identifiers as indexes; consumers file `Claim`s. The lab ships
IP/ASN/VNI pools for the fabric plus a dedicated SRv6 chain:

```bash
kubectl $CTX -n kuid-system get ipindexes,asnindexes,vniindexes
# srv6-locator-v6 2001:db8:feed::/48 <- locator pool
# srv6-sids-v6 2001:db8:feed:100::/64 <- SID pool under it
# srv6-service-ids VNI range 500000-500999 <- SRv6 service IDs
```

Provisioning a service makes a claim appear; withdrawing intent releases it —
shared fabric state and unrelated claims survive:

```bash
kubectl $CTX -n kuid-system get claims -o wide
./tests/integration/update_delete_survivability.sh # proves claim release
```

---

## 4. Building block: SRv6

`SRv6Service` expresses a bidirectional IPv6 VPN: a KUID-allocated locator, two
attachments, a path with primary/alternate waypoints, and an immutable VRF.
Structural CEL rules on the CRD reject bad shapes (non-IPv6 prefixes, identical
endpoints, locator outside /48-/64) at admission time.

Try it directly:

```bash
kubectl $CTX -n agentic-netops-intent apply -f - <<'EOF'
apiVersion: agentic-netops.io/v1alpha1
kind: SRv6Service
metadata:
name: demo-srv6
spec:
topologyRef: agentic-netops-topology
vrf: demo-vrf
underlay: ipv6
locatorPrefix: 2001:db8:feed::/48
servicePrefix: 2001:db8:10::/64
path:
primary: spine01
attachments:
- node: leaf01
interface: ethernet4
- node: leaf02
interface: ethernet4
client:
name: srv6-client01
ip: 2001:db8:10::11/64
server:
name: srv6-client02
ip: 2001:db8:10::22/64
EOF
kubectl $CTX -n agentic-netops-intent get srv6services demo-srv6 -w
```

Verify on the fabric:

```bash
docker exec clab-agentic-netops-fabric-leaf01 vtysh -c 'show segment-routing srv6 locator'
```

Honest limitations to state: full SRv6 conformance runs on the `sonic-vm` profile;
on the virtual-SONiC image the proven path is GCU-write → gNMI read-back witnesses
(locator, `SRV6_MY_SIDS`, counters) — details in
[SRV6_GNMI_CAPABILITY_FINDINGS.md](SRV6_GNMI_CAPABILITY_FINDINGS.md).

---

## 5. Provision services from plain language

Open the console at http://localhost:30000 (the scenario cards are exactly what the
supervisor advertises on `GET /suggested-prompts`), say the prompt, `confirm` twice.

| Prompt | Construct |
|--------|-----------|
| "Provision a vlan 120 on leaf01 ethernet1 for tenant acme" | vlan |
| "Extend vlan 100 as a mac-vrf across leaf01 ethernet2 and leaf02 ethernet2 for tenant blue" | mac-vrf |
| "Give tenant initech an ip-vrf carrying 10.50.0.0/24 on leaf01 wan1" | ip-vrf |
| "Apply an acl on leaf01 ethernet1 and leaf02 ethernet1 for tenant acme: permit tcp 443 from 10.0.0.0/24, deny everything else" | acl |

Watch the tier reason:

```bash
kubectl $CTX -n agentic-netops-agents logs -f deploy/supervisor
```

Prove each one where it counts:

```bash
# vlan: CONFIG_DB on the leaf
docker exec clab-agentic-netops-fabric-leaf01 docker exec sonic /usr/local/bin/sonic-cfggen -d --print-data | grep -A3 VLAN

# mac-vrf: EVPN overlay on BOTH leaves — Remote VTEPs = 1 is the signal that matters
docker exec clab-agentic-netops-fabric-leaf01 vtysh -c 'show evpn vni'
docker exec clab-agentic-netops-fabric-leaf02 vtysh -c 'show evpn vni'

# end-to-end data path
docker exec clab-agentic-netops-fabric-client01 ping -c3 192.0.2.21
```

Also demo the refusal: name a port that does not exist ("leaf01 ethernet99"). The
translator refuses **before** submission and lists the site's real ports. And the
informational class: "What service types do you support?" answers, provisions
nothing.

---

## 6. Retrieving UUIDs and identifiers from Kubernetes

Three layers, each answering a different audit question.

### 6.1 Kubernetes object UUIDs — *which object is this?*

Every CR carries an immutable etcd-level `metadata.uid`:

```bash
# all Networks with their UUIDs
kubectl $CTX -n agentic-netops-intent get networks.network.kubenet.dev -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid,CREATED:.metadata.creationTimestamp'

# SRv6Services as name<TAB>uid
kubectl $CTX -n agentic-netops-intent get srv6services -o 'jsonpath={range .items[*]}{.metadata.name}{"\t"}{.metadata.uid}{"\n"}{end}'

# one object, full metadata including uid
kubectl $CTX -n agentic-netops-intent get network <name> -o jsonpath='{.metadata.uid}'
```

### 6.2 Correlation IDs — *which sentence created it?*

The console stamps each request with a `correlation_id`, carried on the Network as
a label — the thread from chat to CR to events:

```bash
kubectl $CTX -n agentic-netops-intent get networks -l agentic-netops.io/correlation-id=<id-shown-in-the-console> -o 'custom-columns=NAME:.metadata.name,TYPE:.metadata.annotations.agentic-netops\.io/service-type,READY:.status.conditions[?(@.type=="Ready")].status,REASON:.status.conditions[?(@.type=="Ready")].reason'

# the controller's own verdict as events
kubectl $CTX -n agentic-netops-intent get events --field-selector reason=ApplySucceeded
```

A worked example of the whole chain (prompt → correlation id → Ready condition →
fabric evidence) is captured in
[media/agentic-netops-intent-tier-demo-evidence.json](media/agentic-netops-intent-tier-demo-evidence.json).

### 6.3 KUID claims — *which fabric identifiers did it consume?*

Not Kubernetes UUIDs — the actual network identity allocated to each service:

```bash
kubectl $CTX -n kuid-system get claims -o 'custom-columns=NAME:.metadata.name,POOL:.spec.from.name,PURPOSE:.spec.purpose'
```

Together the three layers give a complete audit chain: `metadata.uid` proves which
object you are auditing, the correlation-id proves which intent created it, and the
KUID claim proves which VNI/locator/SID it consumed.

---

## 7. Prove autonomy (optional, ~10 min)

```bash
./tests/integration/drift_preservation.sh # managed paths restored, unmanaged preserved
./tests/integration/failure_recovery_invalid_yang.sh # target killed mid-write still converges
./tests/integration/idempotence.sh # no churn
./tests/integration/update_delete_survivability.sh # claims released, shared state intact
./tests/integration/fabric_verify.sh run # fails closed, never "green by assumption"
```

## 8. Tear down

```bash
./scripts/off.sh --delete-kind true # idempotent; safe to re-run
```

## Troubleshooting quick hits

| Symptom | Fix |
|---------|-----|
| `kubectl top` returns nothing | kind has no metrics-server — expected |
| gNMI `Unauthenticated` | credentials rotate per provision — re-read the `gnmi-lab-creds` secret (TUTORIAL.md appendix) |
| `namespaces "kubenet-system" not found` | wrong kubectl context — check `$CTX` |
| Console up but no reasoning | `LLM_MODEL` empty in `.env` |
| Provision dry-run hits the wrong cluster | `provision.sh` uses your *current* context — set it first |
31 changes: 31 additions & 0 deletions lab/profiles/sonic-vm/bootstrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SONiC VM Profile — Limited Bootstrap

Scope: identical to sonic-vs — management reachability, TLS materials, gNMI service
enablement, and persistence. No underlay/overlay control-plane or data-plane
configuration is applied by this profile; EVPN/SRv6 qualification drives
configuration over gNMI during qualification (scripts/lib/qualify.sh).

VM-specific execution model (differs from sonic-vs):
- The containerlab `sonic-vm` kind runs a QEMU *launcher* container; SONiC runs
inside a VM. `clab::bootstrap` docker-exec's `init-sonic-bootstrap.sh` into the
launcher, which then provisions the guest over SSH (vrnetlab forwards guest
port 22 into the launcher namespace; `sshpass` ships in the vrnetlab sonic
image; factory credentials `admin`/`YourPaSsWoRd` are overridable via
`SONIC_VM_ADMIN_USER` / `SONIC_VM_ADMIN_PASS`).
- TLS key/cert are installed into the guest's /etc/sonic/telemetry/ (mandatory
.cer suffixes, same YANG validation as the VS profile).
- gNMI/telemetry is enabled via TELEMETRY|gnmi + TELEMETRY|certs in the guest
CONFIG_DB (`config load` + `config save` for persistence), TLS only, JSON_IETF
encoding. The gNMI account is created as a Linux user in the guest (PAM/sshd
password auth), with the lab-generated credentials from gnmi_creds.json.
- There is deliberately no configure-fabric-bgp.sh here: it drives FRR/redis via
docker exec, which cannot cross into the VM guest. Fabric configuration is
gNMI-driven by qualification.

Artifacts:
- gnmi_config_db.json — minimal CONFIG_DB snippet enabling gNMI/telemetry with TLS paths.
- install-gnmi-certs.sh — guest-side idempotent TLS installer (invoked over SSH).
- init-sonic-bootstrap.sh — launcher-side orchestrator (SSH into guest, run 1-4).

The image itself is built by scripts/build-sonic-vm.sh (vrnetlab, KVM required)
and pinned in versions.lock.yaml (sonic_images.sonic_vm + sonic_yang.compatibility).
Loading
Loading