diff --git a/docs/changelog-20260709-opentofu-maas-netem.md b/docs/changelog-20260709-opentofu-maas-netem.md new file mode 100644 index 0000000..8180ab6 --- /dev/null +++ b/docs/changelog-20260709-opentofu-maas-netem.md @@ -0,0 +1,84 @@ +# Changelog 2026-07-09 -- MAAS VM-host registration + tc netem modules (DOCFIX-147) + +No live infrastructure touched -- unapplied IaC only. Researched against +official sources before drafting (operator instruction this turn), not +inferred from memory -- continues the DOCFIX-142/144/145/146 OpenTofu build. + +## Items + +### 1. DOCFIX-147 -- `opentofu/modules/maas-vm-host` +FILES (new): `opentofu/modules/maas-vm-host/{main,variables,outputs}.tf`; +root `versions.tf`/`variables.tf`/`main.tf` gained the `canonical/maas` +provider (v2.7.2, confirmed from the registry's own metadata, not guessed) +plus `maas_api_url`/`maas_api_key` variables (`maas_api_key` marked +`sensitive = true`). + +WHAT: registers the vcloud host's virsh/libvirt connection with MAAS as a +VM host (D-103: "OpenTofu registers each DC's libvirt host to that DC's MAAS +rack controller as a virsh VM-host, so MAAS DISCOVERS the OpenTofu-created +node VMs"). + +VERIFICATION, the point of this delivery: read both `maas_vm_host` and +`maas_vm_host_machine`'s real schemas (fetched from +`canonical/terraform-provider-maas`'s own `docs/resources/*.md`) before +picking one. `maas_vm_host_machine` takes `cores`/`memory`/`storage_disks`/ +`network_interfaces` as INPUT arguments -- MAAS's own "Compose machine" pod +feature, where you specify desired specs and MAAS creates a new VM itself +via the virsh connection. D-103 explicitly rules this out ("MAAS ... does +NOT compose new ones"). `maas_vm_host` (register the chassis; MAAS's own +discovery then enlists whatever domains already exist) is the one that +matches D-103's actual described flow, where `modules/node-vm` already +pre-creates the VMs. Using `vm_host_machine` here would have MAAS and +OpenTofu fighting over VM creation -- a real design mistake this research +step caught before any code was written, not after. + +Not instantiated: needs a real MAAS zone/pool and the vcloud host's real +`power_address` (confirmed format `qemu+ssh://user@host/system` from the +provider's own docs example -- same shape as this module's existing +`libvirt_uri` variable, kept as an independent input rather than assumed +identical). + +### 2. DOCFIX-147 (cont.) -- `opentofu/modules/netem-link` +FILES (new): `opentofu/modules/netem-link/{main,variables,outputs}.tf`. + +WHAT: applies `tc qdisc ... netem` WAN-simulation parameters (D-100) to a +`modules/mesh-link` bridge -- the mechanism flagged as missing in every +prior OpenTofu changelog this session ("needs either a null_resource +provisioner or a small companion script -- not yet written"). + +VERIFICATION: fetched OpenTofu's own official docs for `local-exec` and +`terraform_data` before drafting. Two findings that materially shaped the +design: (a) OpenTofu's own docs recommend `terraform_data` over the older +`null_resource` for exactly this "provisioner with no logical resource to +attach to" case -- used here, not `null_resource`. (b) `local-exec` runs on +the machine invoking `tofu apply` (Office1, per D-103), NOT on the vcloud +host where the bridge interfaces actually exist -- a bare local command +would silently do nothing useful. The module wraps the command in an +explicit SSH hop to the vcloud host instead. `tc qdisc replace` (not `add`) +is used so a re-apply is idempotent. A destroy-time provisioner +(`when = destroy`) removes the qdisc on teardown. + +Not instantiated: needs the real bridge name (reading a `libvirt_network`'s +`bridge` attribute back was already flagged unconfirmed in `mesh-link`'s +outputs.tf, so this is a required, explicit input, not auto-derived), the +vcloud host's SSH target, and the actual netem parameters -- still an +unruled D-100 open item. + +`opentofu/README.md` gained a new "MAAS registration + tc netem research" +section (mirroring the OPNsense research section's structure) with full +sourcing, and updated schema notes for `maas_vm_host`, the `maas` provider +block, and `terraform_data`. Workflow doc (Stage 3/4 authoring notes, gap +register items 2/4, summary table) and the companion visual tracker updated +to match. + +REVERT: `git rm -r opentofu/modules/maas-vm-host opentofu/modules/netem-link`; +revert `opentofu/versions.tf`/`variables.tf`/`main.tf`'s maas-provider +additions and the doc sections if reverting fully. + +## Next actionable step + +Same underlying need as every prior OpenTofu delivery this session: a +machine with the `tofu` binary to run `scripts/opentofu-validate.sh` against +all of `opentofu/` -- this would catch any remaining schema mistakes at +once, the same way it would have caught the DOCFIX-142 syntax bug +immediately had it been available then. diff --git a/docs/dc-dc-deployment-workflow.md b/docs/dc-dc-deployment-workflow.md index fa16305..6b6001c 100644 --- a/docs/dc-dc-deployment-workflow.md +++ b/docs/dc-dc-deployment-workflow.md @@ -79,7 +79,7 @@ | **Gate** | MAAS rack controller per DC up; MAAS discovers the OpenTofu-created node VMs; edge routing + simulated ISP uplink up; netem parameters applied and measured. | | **Owns** | D-103 (seam), D-100 (edges). | | **Reuse vs new** | NEW (no OpenTofu/multi-rack precedent in VR0 DC0). Explicitly tagged shim: node-VM creation has no Roosevelt analog (physical racking + BMC enlistment replaces it there) -- do not treat as reusable production IaC. | -| **Authoring status** | Runbook not started. The plane/link/pool layer of `opentofu/` (see Stage 1) is reusable here too. `modules/node-vm` (2026-07-09) covers the MAAS-managed OpenStack node VM pattern (blank disk, PXE-boot) -- not yet instantiated (node count/sizing is a pending Phase-0 decision). Still missing for THIS stage: the virsh-VM-host-to-MAAS-rack registration and the OPNsense edge itself (OPNsense needs the DIFFERENT cloud-init/base-image VM pattern, not `node-vm`'s PXE pattern -- still unbuilt). See `opentofu/README.md`. | +| **Authoring status** | Runbook not started. The plane/link/pool layer of `opentofu/` (see Stage 1) is reusable here too. `modules/node-vm` (blank disk, PXE-boot), `modules/opnsense-edge`, and `modules/maas-vm-host` (2026-07-09, registers the virsh host with MAAS via the official `canonical/maas` provider -- deliberately NOT `maas_vm_host_machine`, which composes new VMs and would fight `node-vm`) all now exist -- none instantiated (node sizing, OPNsense config.xml content, and a real MAAS zone/pool are all still pending decisions). `modules/netem-link` gives the netem mechanism for this stage's edge gate too. See `opentofu/README.md`. | **State:** NOT STARTED. @@ -191,32 +191,43 @@ a `$DC` (or `--dc`) selector, with both libraries becoming DC-keyed lookups instead of flat constants. Do this BEFORE writing the Stage 5 runbook, or the "high reuse" claim above is not actually true operationally. -2. **OpenTofu -- network/pool/node-VM/cloud-init layer scaffolded 2026-07-09 - (was: zero files).** `opentofu/` now has: `modules/dc-planes` (six per-DC - planes), `modules/mesh-link` (the D-100 dark-fiber triangle legs), - `modules/dc-storage-pool`, `modules/node-vm` (blank disk, PXE-boot - MAAS-managed node VM, the D-103 pattern for Stage 3), and - `modules/base-image` + `modules/cloudinit-vm` (the OTHER VM pattern -- - pre-built image + cloud-init, for Office1's own service VMs). DC1 + Office1 - pool/plane wiring is instantiated in root `main.tf`; `node-vm` and - `cloudinit-vm` are NOT (node sizing and Office1 VM image/config choices are - both pending decisions -- neither invented here). Provider schema was - confirmed against the actual current docs AND real example `.tf` files - (not memory) -- it differs materially from older common examples: nested - structures are attribute-style objects (`key = { ... }`), not classic HCL - blocks. **A real bug from the first pass was found and fixed:** - `dc-planes`/`mesh-link`/`dc-storage-pool` originally used block syntax for - `libvirt_network`'s `domain`/`mtu` and `libvirt_pool`'s `target`; corrected - once real provider examples surfaced the actual convention -- discovered - precisely BECAUSE building the domain module forced fetching real example - code instead of doc-summaries alone. Still missing, deliberately deferred - (see `opentofu/README.md`): OPNsense's own bootability via `cloudinit-vm` - is NOT confirmed (FreeBSD, not the same cloud-init/NoCloud guarantee as - Linux images); the per-device `boot`-order attribute's exact internal - shape (flagged UNVERIFIED in `node-vm/main.tf`, an inference by pattern - not a confirmed field); and `tc netem` application. **UNVALIDATED** -- no - `tofu` binary was available to run `scripts/opentofu-validate.sh` this - session; run it on a connected machine before trusting any of this. +2. **OpenTofu -- network/pool/node-VM/cloud-init/MAAS/netem layer scaffolded + 2026-07-09 (was: zero files).** `opentofu/` now has: `modules/dc-planes` + (six per-DC planes), `modules/mesh-link` (the D-100 dark-fiber triangle + legs), `modules/dc-storage-pool`, `modules/node-vm` (blank disk, PXE-boot + MAAS-managed node VM, the D-103 pattern for Stage 3), `modules/base-image` + + `modules/cloudinit-vm` (the OTHER VM pattern -- pre-built image + + cloud-init, for Office1's own service VMs), `modules/opnsense-edge` (see + gap #4), `modules/maas-vm-host` (registers the vcloud host with MAAS as a + virsh VM host -- via the official `canonical/maas` provider, confirmed + against its real schema; deliberately NOT `maas_vm_host_machine`, which + composes/creates new VMs itself and would fight `node-vm` for VM + creation -- D-103 explicitly rules out MAAS composing), and + `modules/netem-link` (`terraform_data` + SSH-wrapped `local-exec` + provisioners applying `tc qdisc ... netem` to a mesh-link bridge -- + OpenTofu runs from Office1, not the vcloud host, so a bare local command + can't reach the bridge). DC1 + Office1 pool/plane wiring is instantiated + in root `main.tf`; everything else is NOT (node/VM sizing, MAAS zone/pool, + netem parameters are all pending decisions -- neither invented here). + Provider schema was confirmed against the actual current docs AND real + example `.tf` files (not memory) -- it differs materially from older + common examples: nested structures are attribute-style objects (`key = + { ... }`), not classic HCL blocks. **A real bug from the first pass was + found and fixed:** `dc-planes`/`mesh-link`/`dc-storage-pool` originally + used block syntax for `libvirt_network`'s `domain`/`mtu` and + `libvirt_pool`'s `target`; corrected once real provider examples surfaced + the actual convention -- discovered precisely BECAUSE building the domain + module forced fetching real example code instead of doc-summaries alone. + Still missing, deliberately deferred (see `opentofu/README.md`): + OPNsense's own bootability via `cloudinit-vm` is NOT confirmed (FreeBSD, + not the same cloud-init/NoCloud guarantee as Linux images); the + per-device `boot`-order attribute's exact internal shape (flagged + UNVERIFIED in `node-vm/main.tf`, an inference by pattern not a confirmed + field); and whether `devices.interfaces` list order reliably maps to + LAN/WAN NIC enumeration (flagged in `opnsense-edge/main.tf`). + **UNVALIDATED** -- no `tofu` binary was available to run + `scripts/opentofu-validate.sh` this session; run it on a connected + machine before trusting any of this. 3. **NetBox multi-DC/dual-stack import pipeline.** `netbox/` has exactly two scripts (`ipv4-prefixes-import.py`, `ipv6-mark-reserved.py`) -- single-DC v4 import and v6-reservation-marking only. D-101 requires DC2's supernet, the @@ -248,14 +259,15 @@ OPNsense boot were available to confirm any of it end-to-end. Real `config.xml` content per site is still design work, no fallback provided. Full sourcing in `opentofu/README.md`. - - **(d) `tc netem` WAN-simulation -- a wholly separate mechanism, not an - OPNsense setting.** No Terraform/OpenTofu-native equivalent exists at - all (confirmed against the `libvirt_network` schema, gap #2); it is a - Linux `tc qdisc` setting applied to the host-side bridge interface after - the network exists, needing either a provisioner or a small companion - script -- neither written. The exact parameters (latency/jitter/loss/ - rate) are themselves still an unruled D-100 open sub-item (gap #11), so - this needs a decision before it can be scripted, not just scripting. + - **(d) `tc netem` WAN-simulation -- mechanism BUILT (`modules/netem-link`), + only the real parameters remain.** No Terraform/OpenTofu-native + equivalent exists (confirmed against the `libvirt_network` schema, gap + #2); built instead as a `terraform_data` + SSH-wrapped `local-exec` + provisioner (OpenTofu runs from Office1, not the vcloud host where the + bridge lives -- a bare local command can't reach it). UNVALIDATED, not + instantiated. The exact parameters (latency/jitter/loss/rate) are + themselves still an unruled D-100 open sub-item (gap #11), so this + needs a decision before it can be applied for real, not just scripting. 5. **Ceph replication tooling (radosgw multisite + rbd-mirror).** No script in the repo touches either (checked; `osd-blank-check.sh` is unrelated). D-108's entire mechanism -- zonegroup setup, rbd-mirror daemon peering, the @@ -302,7 +314,9 @@ | Stage 0-7 runbooks | NOT YET WRITTEN -- see Tooling gap register above for the itemized breakdown | | `opentofu/` (networks/pools/node-VM PXE/cloud-init-VM patterns) | SCAFFOLDED 2026-07-09, UNVALIDATED (no `tofu` binary available this session) -- see gap #2 and `opentofu/README.md` | | OPNsense image+config mechanism (`modules/opnsense-edge`) | BUILT 2026-07-09, UNVALIDATED (no `tofu`/ISO-tool/real boot to confirm) -- real config.xml content per site still design work, see `opentofu/README.md` | -| `opentofu/` (netem, DC2 planes) | NOT DONE -- deliberately deferred, see `opentofu/README.md` | +| MAAS VM-host registration (`modules/maas-vm-host`) | BUILT 2026-07-09, UNVALIDATED -- needs a real MAAS zone/pool + vcloud power_address, see `opentofu/README.md` | +| `tc netem` mechanism (`modules/netem-link`) | BUILT 2026-07-09, UNVALIDATED -- real latency/jitter/loss/rate parameters still an unruled D-100 item, see `opentofu/README.md` | +| `opentofu/` (DC2 planes) | NOT DONE -- deliberately deferred pending NetBox CIDR assignment, see `opentofu/README.md` | | Reusable as-is, repo-agnostic (2026-07-09 sweep) | `bundle.yaml`, `phase-01..08` VR0 DC0 runbooks (Stage 5/7 template, once DC-parameterized -- gap #1), `preflight.sh`, `cloud-assert.sh`, `repo-lint.sh`, `run-logged.sh`, `ledger-scan.sh` | | `lib-net.sh` / `lib-hosts.sh` | NOT yet DC-agnostic -- single-deployment hardcoded literals; see gap #1 | | Environment/skill docs (`environment.md`, README.md) | Still describe VR0 DC0/v1 single-DC scope wholesale -- flagged, not yet rewritten for VR1; not a blocker for Stage 0 but will need doing before/during Stage 1 | diff --git a/docs/session-ledger.md b/docs/session-ledger.md index 7f59fcf..8afbe31 100644 --- a/docs/session-ledger.md +++ b/docs/session-ledger.md @@ -824,8 +824,28 @@ enumeration for this guest. `config_iso_path` has no default -- real config.xml content per site is still undesigned. Not instantiated in root `main.tf`. `opentofu/README.md` + workflow doc + visual tracker updated to - reflect BUILT (not just researched). Uncommitted -- operator has not yet - asked to commit this piece. + reflect BUILT (not just researched). COMMITTED + PUSHED (a3ff07f). +- **DONE -- DOCFIX-147 (changelog `docs/changelog-20260709-opentofu-maas-netem.md`):** + operator asked to pull docs and validate before drafting the next modules + -- the two remaining unbuilt mechanisms from the OpenTofu scope + (MAAS VM-host registration, tc netem application). Researched a real + official MAAS provider (`canonical/maas` v2.7.2) and OpenTofu's own + `local-exec`/`terraform_data` docs BEFORE writing any code, per + instruction. Caught a real design mistake before it happened: read both + `maas_vm_host` (register) and `maas_vm_host_machine` (compose -- takes + cores/memory/disks as INPUTS, MAAS's "Compose machine" pod feature) + schemas, cross-checked against D-103's explicit "MAAS does NOT compose new + ones" ruling, and built `modules/maas-vm-host` using ONLY `maas_vm_host` -- + the other resource would have had MAAS and `node-vm` fighting over VM + creation. Also confirmed `local-exec` runs on the machine invoking `tofu + apply` (Office1 per D-103), not the vcloud host where bridges live -- + `modules/netem-link` wraps its `tc qdisc` command in an explicit SSH hop + because of this, using `terraform_data` (OpenTofu's own recommended + replacement for `null_resource`) with a destroy-time provisioner to clean + up. Neither module instantiated (MAAS zone/pool, vcloud SSH target, and + real netem parameters all still pending). `opentofu/README.md` gained a + new sourced research section; workflow doc + visual tracker updated. + Uncommitted -- operator has not yet asked to commit this piece. diff --git a/opentofu/README.md b/opentofu/README.md index 22fdbdb..fbc91ed 100644 --- a/opentofu/README.md +++ b/opentofu/README.md @@ -69,8 +69,26 @@ download/convert/build behavior; see each script's header. `config_iso_path` has no default: real `config.xml` content per site is design work not done. Not instantiated in root `main.tf`. +- `modules/maas-vm-host` (2026-07-09) -- registers the vcloud host's virsh/ + libvirt connection with MAAS as a VM host, via the official `canonical/maas` + provider's `maas_vm_host` resource (D-103: "OpenTofu registers each DC's + libvirt host to that DC's MAAS rack controller as a virsh VM-host, so MAAS + DISCOVERS the OpenTofu-created node VMs"). Deliberately does NOT use + `maas_vm_host_machine` -- see the dedicated research section below for why + that resource is the wrong one (it composes new VMs; D-103 explicitly rules + that out). Not instantiated: needs a real MAAS zone/pool and the vcloud + host's real power_address. +- `modules/netem-link` (2026-07-09) -- applies `tc qdisc ... netem` + WAN-simulation parameters (D-100) to a `mesh-link` bridge, via a + `terraform_data` resource with `local-exec` provisioners run over SSH (NOT + a bare local command -- OpenTofu runs from Office1, not the vcloud host + where the bridges live). See the dedicated research section below. Not + instantiated: needs the real bridge name, the vcloud host's SSH target, + and the actual netem parameters (still an unruled D-100 decision). - Root wiring for DC1 (planes + pool, using DC1's inherited DC0 CIDRs) and - Office1 (pool only), plus all three mesh links. + Office1 (pool only), plus all three mesh links, plus a `provider "maas" {}` + block (api_url/api_key as new required variables, `maas_api_key` marked + `sensitive = true`). **Syntax bug fixed 2026-07-09:** `dc-planes`, `mesh-link`, and `dc-storage-pool` originally used classic HCL block syntax (`domain { name = ... }`) for @@ -90,23 +108,19 @@ explicit open sub-item). `main.tf` has the DC2 module block written and commented out, ready to uncomment once NetBox assigns real CIDRs -- do not fill in a guessed default to make it "work" sooner. -- **`tc netem` WAN simulation.** `libvirt_network` has no netem-equivalent - argument; this is an OS-level `tc qdisc` setting applied to the bridge - interface a `mesh-link` module creates, which needs either a `null_resource` - provisioner or a small companion script -- not yet written. The exact - parameters are themselves a Phase-0 open item (D-100), so this was going to - need a second pass regardless. -- **OPNsense edges -- (a)-(c) DONE 2026-07-09, only (d) remains:** (a) network - substrate (`modules/mesh-link`); (b)+(c) the image AND its config, built as - `modules/opnsense-edge` + the two prep scripts above -- UNVALIDATED (no real - boot, no `tofu` binary, no ISO-building tool available this session; see - each file's header for exactly what's confirmed vs. assumed); real +- **OPNsense edges -- (a)-(c) DONE 2026-07-09, (d) mechanism now also DONE, + only real parameters/instantiation remain:** (a) network substrate + (`modules/mesh-link`); (b)+(c) the image AND its config + (`modules/opnsense-edge` + two prep scripts) -- UNVALIDATED (no real boot, + no `tofu` binary, no ISO-building tool available this session); real `config.xml` content per site is still design work, not yet done, and the - module has no fallback for it. (d) `tc netem` WAN simulation is a separate - OS-level `tc qdisc` setting with no Terraform-native equivalent at all, - needing its own provisioner/script, and whose parameters are still an - unruled D-100 open item -- a decision, not just scripting, is needed first. + module has no fallback for it. (d) `tc netem` -- the injection MECHANISM is + now built (`modules/netem-link`), but the actual latency/jitter/loss/rate + values are still an unruled D-100 open item, so nothing is instantiated. Full breakdown in `docs/dc-dc-deployment-workflow.md` gap register item 4. +- **MAAS VM-host registration -- mechanism DONE (`modules/maas-vm-host`), not + instantiated.** Needs a real MAAS zone/pool and the vcloud host's power + address; neither exists yet. - **MAAS-region / NetBox / GitBucket service VMs -- module exists (`cloudinit-vm`), not instantiated.** No image source has been chosen for any of them, and their actual `user_data`/`meta_data`/`network_config` @@ -183,6 +197,57 @@ no ISO-building tool, and no real OPNsense boot were available this session to confirm any of it end-to-end). +## MAAS registration + tc netem research (2026-07-09) -- the basis for `modules/maas-vm-host` and `modules/netem-link` + +Researched directly against the `canonical/terraform-provider-maas` provider's +own docs and OpenTofu's own official docs, not inferred. + +1. **A real, official MAAS provider exists: `canonical/maas`**, v2.7.2 + (confirmed from the registry page metadata, published 2026-01-30) -- not + a community/unofficial one. Moved from an older `maas/maas` namespace; + use `canonical/maas`. + (https://registry.terraform.io/providers/canonical/maas/latest) +2. **`maas_vm_host` (register) vs. `maas_vm_host_machine` (compose) are + DIFFERENT operations -- confirmed by reading both resources' real + schemas, not just their names.** `maas_vm_host_machine`'s arguments + (`cores`, `memory`, `storage_disks`, `network_interfaces` as INPUTS) are + MAAS's own "Compose machine" pod feature -- you specify desired specs and + MAAS creates a new VM via the virsh connection itself. D-103 explicitly + rules this out: "MAAS owns commission / deploy / power / release of those + node VMs; it does NOT compose new ones." `maas_vm_host` (register the + virsh chassis; MAAS's own discovery then enlists whatever domains already + exist there) is the one that matches D-103's actual described flow -- + `modules/node-vm` pre-creates the VMs, registering the host is what makes + MAAS find them. Using `vm_host_machine` here would have MAAS and OpenTofu + fighting over VM creation. + (https://raw.githubusercontent.com/canonical/terraform-provider-maas/master/docs/resources/vm_host.md, + .../vm_host_machine.md) +3. **`power_address` confirmed in the exact virsh URI format already used + elsewhere in this repo** -- the provider's own docs example is + `"qemu+ssh://172.16.99.2/system"`, the same shape as this module's own + `libvirt_uri` variable. In practice these should point at the same vcloud + host, kept as independent inputs rather than silently assumed identical. +4. **`local-exec` runs on the machine invoking `tofu apply` (Office1), NOT on + the resource or the vcloud host** -- confirmed from OpenTofu's own + official docs. This matters directly: D-103 says OpenTofu itself runs + from the Office1 operator VM, so a bare `local-exec` command applying + `tc qdisc` would run on Office1, not touch the vcloud host's bridge + interfaces at all. `modules/netem-link` wraps the command in an explicit + SSH hop to the vcloud host because of this. + (https://opentofu.org/docs/language/resources/provisioners/local-exec/) +5. **OpenTofu's own docs recommend `terraform_data` over the older + `null_resource`** for exactly the "run a provisioner with no logical + resource to attach it to" case -- a built-in resource, no provider needed. + Used in `modules/netem-link`, not `null_resource`. + (https://opentofu.org/docs/language/resources/tf-data/) + +**Built from this research (2026-07-09):** `modules/maas-vm-host` +(registration only, deliberately no composition), `modules/netem-link` +(`terraform_data` + SSH-wrapped `local-exec`, with a destroy-time +provisioner to remove the qdisc on teardown). Neither instantiated: both +need real inputs (a MAAS zone/pool, the vcloud host's SSH target, real +netem parameters) that don't exist yet. + ## Schema notes (read before extending) Verified this session against the provider's actual current docs @@ -251,6 +316,23 @@ (it's exactly what the real example does), but the mechanism wasn't named explicitly in the docs pages fetched this session -- confirm with `tofu plan` if it errors on a `file://`-less bare path. +- `maas_vm_host`'s `type` argument accepts `"virsh"` or `"lxd"` (confirmed + from the provider's own docs); `power_address` is a flat string, no nested + attributes; `zone`/`pool` are flat optional strings, not objects. Simpler, + flatter schema than the libvirt provider's -- confirmed directly from + `docs/resources/vm_host.md` in `canonical/terraform-provider-maas`. +- `provider "maas" { ... }` takes flat top-level arguments (`api_url`, + `api_key`, `api_version` [defaults `"2.0"`], `installation_method` + [defaults `"snap"`], `tls_ca_cert_path`, `tls_insecure_skip_verify`) -- + confirmed from the provider's own `docs/index.md`, including its exact + example block. +- `terraform_data`'s schema is `input` (optional, stored + reflected in + `output`) and `triggers_replace` (optional, forces replacement when + changed) -- confirmed from OpenTofu's own official docs, including a + complete example using it with a `local-exec` provisioner keyed on + `triggers_replace`. `modules/netem-link` uses `triggers_replace` (not + `input`) since nothing needs to be stored/reflected, just re-triggered + when the bridge or netem args change. ## Conventions carried from the rest of this repo diff --git a/opentofu/main.tf b/opentofu/main.tf index bec3169..60e0dfb 100644 --- a/opentofu/main.tf +++ b/opentofu/main.tf @@ -6,6 +6,18 @@ uri = var.libvirt_uri } +provider "maas" { + api_url = var.maas_api_url + api_key = var.maas_api_key +} + +# ---- modules/maas-vm-host and modules/netem-link are NOT instantiated below +# yet: both need real, measured inputs (a MAAS zone/pool that exists, the +# vcloud host's SSH target, real netem parameters) that don't exist yet -- +# see docs/dc-dc-deployment-workflow.md gap register items 2/4/11 and +# opentofu/README.md. The provider block above is wired so the modules are +# ready to call once those values are real. ---- + # ---- DC1: inherits the DC0 six-plane v4 layout unchanged (D-101) ---- module "dc1_planes" { diff --git a/opentofu/modules/maas-vm-host/main.tf b/opentofu/modules/maas-vm-host/main.tf new file mode 100644 index 0000000..de99dad --- /dev/null +++ b/opentofu/modules/maas-vm-host/main.tf @@ -0,0 +1,29 @@ +# maas-vm-host: registers the vcloud host's libvirt/virsh connection with +# MAAS as a VM host (D-103: "OpenTofu registers each DC's libvirt host to +# that DC's MAAS rack controller as a virsh VM-host, so MAAS DISCOVERS the +# OpenTofu-created node VMs"). +# +# DELIBERATELY uses `maas_vm_host`, NOT `maas_vm_host_machine` -- confirmed +# by reading both resources' real schemas (canonical/terraform-provider-maas, +# fetched 2026-07-09): `maas_vm_host_machine` takes `cores`/`memory`/ +# `storage_disks`/`network_interfaces` as INPUT arguments, i.e. it tells MAAS +# to COMPOSE (allocate + create) a brand-new VM from the host's resource +# pool -- MAAS's own "Compose machine" pod feature. D-103 explicitly rules +# this out: "MAAS owns commission / deploy / power / release of those node +# VMs; it does NOT compose new ones." `modules/node-vm` already pre-creates +# the VMs OpenTofu's own way; registering the host via `maas_vm_host` alone +# is what makes MAAS's chassis-discovery process find and enlist those +# already-existing domains, matching the D-103 flow exactly. Do not add a +# `maas_vm_host_machine` call here -- it would fight node-vm for VM creation. +# +# Requires a `provider "maas" {}` block in the ROOT module (api_url/api_key +# -- see opentofu/README.md); this module itself declares no provider block, +# matching the convention every other module in this repo already follows. + +resource "maas_vm_host" "dc" { + type = "virsh" + name = var.vm_host_name + power_address = var.power_address + zone = var.zone + pool = var.pool +} diff --git a/opentofu/modules/maas-vm-host/outputs.tf b/opentofu/modules/maas-vm-host/outputs.tf new file mode 100644 index 0000000..ade30aa --- /dev/null +++ b/opentofu/modules/maas-vm-host/outputs.tf @@ -0,0 +1,14 @@ +output "id" { + description = "maas_vm_host id." + value = maas_vm_host.dc.id +} + +output "resources_cores_total" { + description = "Read-only, MAAS-reported total cores on this host -- a real post-registration fact, useful for cross-checking against the Phase-0 host/disk-budget math (buildout-design Section 3), not an input." + value = maas_vm_host.dc.resources_cores_total +} + +output "resources_memory_total" { + description = "Read-only, MAAS-reported total memory on this host." + value = maas_vm_host.dc.resources_memory_total +} diff --git a/opentofu/modules/maas-vm-host/variables.tf b/opentofu/modules/maas-vm-host/variables.tf new file mode 100644 index 0000000..c348cfb --- /dev/null +++ b/opentofu/modules/maas-vm-host/variables.tf @@ -0,0 +1,30 @@ +variable "vm_host_name" { + description = "MAAS VM host (pod) name, e.g. \"dc1-vcloud\"." + type = string +} + +variable "power_address" { + description = <<-EOT + The virsh/libvirt connection URI MAAS uses to reach the vcloud host, e.g. + "qemu+ssh://user@vcloud-host/system" (the exact format confirmed against + the provider's own docs/resources/vm_host.md example). MUST be measured + this session -- no default. This is the SAME libvirt endpoint OpenTofu's + own `provider "libvirt"` block connects to (D-103: OpenTofu and MAAS both + reach the vcloud host's libvirt), so it should match `var.libvirt_uri` in + the root module in practice, but is kept as its own input here rather + than silently assumed identical. + EOT + type = string +} + +variable "zone" { + description = "MAAS zone name for this VM host. Optional in the provider's own schema -- null lets MAAS apply its own default rather than this module guessing one." + type = string + default = null +} + +variable "pool" { + description = "MAAS resource pool name for this VM host. Optional in the provider's own schema -- null lets MAAS apply its own default rather than this module guessing one." + type = string + default = null +} diff --git a/opentofu/modules/netem-link/main.tf b/opentofu/modules/netem-link/main.tf new file mode 100644 index 0000000..b0a64db --- /dev/null +++ b/opentofu/modules/netem-link/main.tf @@ -0,0 +1,34 @@ +# netem-link: applies `tc qdisc ... netem` WAN-simulation parameters to a +# modules/mesh-link bridge (D-100). Confirmed mechanism this session, +# fetched directly from OpenTofu's own official docs (opentofu.org, tag +# current as of 2026-07-09): `libvirt_network` has no netem-equivalent +# argument at all, so this has to happen as a provisioner-only side effect, +# not a libvirt resource. OpenTofu's own docs recommend `terraform_data` +# over the older `null_resource` for exactly this "provisioner with no +# logical managed resource to attach to" case -- used here, not null_resource. +# +# Runs over SSH, not a bare local-exec command: OpenTofu itself runs from +# the Office1 operator VM (D-103), not on the vcloud host where the bridge +# interfaces exist, so the command has to hop there explicitly. Assumes +# passwordless SSH + passwordless sudo to var.vcloud_host_ssh_target as this +# repo's usual jumphost-session conventions provide -- not configured here. +# +# UNVERIFIED, flagged plainly: this whole resource has not been run for +# real this session (no `tofu` binary, no live vcloud host, no bridge to +# test against). `tc qdisc replace` (not `add`) is used deliberately so a +# re-apply is idempotent rather than erroring "RTNETLINK answers: File +# exists" -- a standard, well-known tc usage pattern, not something specific +# to this repo. + +resource "terraform_data" "netem" { + triggers_replace = [var.bridge_name, var.netem_args] + + provisioner "local-exec" { + command = "ssh ${var.vcloud_host_ssh_target} 'sudo tc qdisc replace dev ${var.bridge_name} root netem ${var.netem_args}'" + } + + provisioner "local-exec" { + when = destroy + command = "ssh ${var.vcloud_host_ssh_target} 'sudo tc qdisc del dev ${var.bridge_name} root' || true" + } +} diff --git a/opentofu/modules/netem-link/outputs.tf b/opentofu/modules/netem-link/outputs.tf new file mode 100644 index 0000000..c8337d4 --- /dev/null +++ b/opentofu/modules/netem-link/outputs.tf @@ -0,0 +1,4 @@ +output "id" { + description = "terraform_data resource id -- for depends_on/triggers chaining elsewhere if needed." + value = terraform_data.netem.id +} diff --git a/opentofu/modules/netem-link/variables.tf b/opentofu/modules/netem-link/variables.tf new file mode 100644 index 0000000..49e85db --- /dev/null +++ b/opentofu/modules/netem-link/variables.tf @@ -0,0 +1,46 @@ +variable "link_name" { + description = "Identifier for this link, e.g. \"dc1-dc2\" -- used only for resource naming/logging, matches modules/mesh-link's link_name convention." + type = string +} + +variable "vcloud_host_ssh_target" { + description = <<-EOT + SSH target for the vcloud host, e.g. "user@vcloud-host" -- REQUIRED + because OpenTofu itself runs from the Office1 operator VM (D-103), not on + the vcloud host where the bridge interfaces actually live; a plain + local-exec provisioner runs commands on the machine invoking `tofu + apply` (Office1), so applying tc netem to a vcloud-host bridge needs an + explicit SSH hop, not a bare local command. Passwordless (key-based) SSH + + passwordless sudo on the vcloud host for the invoking user are assumed + prerequisites, not configured by this module. No default -- measured. + EOT + type = string +} + +variable "bridge_name" { + description = <<-EOT + The OS-level Linux bridge interface name backing a modules/mesh-link + network. NOT auto-derived from the mesh-link module: reading a + libvirt_network's `bridge` attribute back when the resource didn't set + it explicitly is unconfirmed this session (see modules/mesh-link's + outputs.tf note). Confirm the real bridge name with `virsh net-info + ` (or `tofu show` post-apply) and pass it explicitly here + -- no default. + EOT + type = string +} + +variable "netem_args" { + description = <<-EOT + The raw arguments passed to `tc qdisc ... root netem ` -- + e.g. "delay 5ms 1ms distribution normal loss 0.1%". No default: the + exact latency/jitter/loss/rate parameters are still an unruled D-100 + Phase-0 decision (buildout-design Section 6/10) -- this module provides + the injection mechanism, not the parameter choice. tc's own netem syntax + is stable, long-standing kernel functionality and was not re-verified + this session (out of scope -- the OpenTofu-side mechanism was the + research target, not tc itself); confirm your exact netem_args string + with `man tc-netem` or a live `tc qdisc` test before relying on it. + EOT + type = string +} diff --git a/opentofu/variables.tf b/opentofu/variables.tf index f9342e2..75c5b79 100644 --- a/opentofu/variables.tf +++ b/opentofu/variables.tf @@ -3,6 +3,17 @@ type = string } +variable "maas_api_url" { + description = "MAAS API endpoint, e.g. http://:5240/MAAS (confirmed format from the canonical/maas provider's own docs). Measured, no default." + type = string +} + +variable "maas_api_key" { + description = "MAAS API key for the account OpenTofu authenticates as. Retrieved via the operator's own secret-handling process -- never hardcoded, never printed (this repo's own secrets discipline applies here same as everywhere else). No default." + type = string + sensitive = true +} + variable "domain_suffix" { description = "Base domain for per-plane libvirt DNS domains. Anchor value per D-106's naming convention (.omega..vr1.cloud.neumatrix.local)." type = string diff --git a/opentofu/versions.tf b/opentofu/versions.tf index a962f94..068ae95 100644 --- a/opentofu/versions.tf +++ b/opentofu/versions.tf @@ -1,8 +1,8 @@ -# Provider version CONFIRMED this session (2026-07-09) against the actual -# releases page: dmacvicar/libvirt latest tag is v0.9.8. Pinned to that exact -# release rather than a range -- re-verify and bump deliberately, don't drift. -# (This repo has no OpenTofu/tofu binary available to it this session -- this -# file, and everything under opentofu/, is UNVALIDATED: run +# Provider versions CONFIRMED against actual registry/release data, not +# memory (2026-07-09 for libvirt; 2026-07-09 for maas). Pinned to exact +# releases rather than a range -- re-verify and bump deliberately, don't +# drift. (This repo has no OpenTofu/tofu binary available to it this +# session -- this file, and everything under opentofu/, is UNVALIDATED: run # `bash scripts/opentofu-validate.sh` on a machine with the tofu binary and # network access to the provider registry before trusting any of it.) @@ -14,5 +14,9 @@ source = "dmacvicar/libvirt" version = "0.9.8" } + maas = { + source = "canonical/maas" + version = "2.7.2" + } } }