From b7438d899c48c0b770f473a0d815d375282c46ad Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 18 Aug 2026 14:11:24 -0500 Subject: [PATCH 1/3] docs: reconcile layout audit with the release-notes work The audit landed in #2190, just before the release-notes work in #2191. Re-check every claim against main: counts move to 137 pages, 8 nav entries, 61 external markdown files and v0.4.28. Add three mechanics the reorg must respect -- the generated-but-navigated unreleased.md, include_dir_to_nav's global sort toggle, and the published release-body URLs that pin docs/release-notes/ permanently. Grow the proposal a seventh tab. Also fix the audit's own errors: the rename impact lists, Phase 3's link count, and a component table row that was never there. --- docs-layout-audit.md | 216 ++++++++++++++++++++++++++++++++----------- 1 file changed, 163 insertions(+), 53 deletions(-) diff --git a/docs-layout-audit.md b/docs-layout-audit.md index 6764f2e4d..9ddfa980a 100644 --- a/docs-layout-audit.md +++ b/docs-layout-audit.md @@ -28,33 +28,42 @@ configuring the network — it hands all three the same flat page list. ## What is there now -133 markdown pages, all of them listed in the hand-maintained `nav:`. Seven +137 markdown pages, all of them listed in the hand-maintained `nav:`. Eight top-level entries: `Home`, `Overview`, `Design Guide`, `Deployment Guide`, -`Operator Guide`, `User Guide`, `Workflows`. +`Operator Guide`, `Release Notes`, `User Guide`, `Workflows`. -Two pages are generated at build time (`docs/workflows/` from -`scripts/argo-workflows-to-mkdocs.py`, and the neutron sample config from -`oslo-config-generator`), and 55 of the 133 are component reference pages under -`docs/deploy-guide/components/`. +Three things are generated at build time — `docs/workflows/` from +`scripts/argo-workflows-to-mkdocs.py`, the neutron sample config from +`oslo-config-generator`, and `docs/release-notes/unreleased.md` from the +`changelog.d/` fragments via `make unreleased-notes` (scriv) — and 56 of the 137 +are component reference pages under `docs/deploy-guide/components/`. -Separately, **58 markdown files live outside `docs/`** and are invisible to the +Separately, **61 markdown files live outside `docs/`** and are invisible to the site. Several are substantial: `go/ironic-hardware-exporter/README.md` (436 lines), `python/understack-tests/README.md` (379), `charts/argocd-understack/README.md` (268), `go/dexop/README.md` (231), -`ansible/README.md` (162). There is also a third docs tree at -`workflows/argo-events/docs/`. +`RELEASING.md` (167), `ansible/README.md` (162). There is also a third docs tree +at `workflows/argo-events/docs/`. ## Findings ### The big one: no contributor documentation exists There is no contributor section on the site and no `CONTRIBUTING.md` in the -repository. Everything a new developer needs is in those 58 external files, which +repository. Everything a new developer needs is in those 61 external files, which are unlinked, unlinted for links, and drifting. The clearest symptom is three near-identical `DEVELOPMENT.md` files (`python/ironic-understack/`, `python/neutron-understack/`, `python/understack-workflows/`) that differ by a handful of lines — the classic copy-paste decay. +The release-notes work (#2191) has since added a 167-line root `RELEASING.md` +that documents the fragment workflow, the tagging process and the CI gate. It is +squarely contributor documentation, it is the one external file that is actually +current, and it is still invisible to the site — a new contributor only finds it +because `.github/pull_request_template.md` and the CI failure message point at +it. It makes the case for the Contributing tab rather than weakening it: the +process is now written down, just not anywhere a reader browses. + Meanwhile `go/nautobotop/README.md` is still unmodified kubebuilder scaffolding: its H1 is `# rax` and it contains three `TODO(user)` placeholders, while `docs/operator-guide/nautobotop.md` is a real 772-line document. @@ -129,7 +138,9 @@ marking which pages are theirs. `nginx.ingress.kubernetes.io/proxy-redirect-*` annotations, while `operator-guide/gateway-api.md` documents the migration off ingress-nginx to Envoy Gateway. This page is in the Quick Start path, so new deployers hit it - early. + early. Both `docs/release-notes/index.md` and `v0.4.md` now cite + `gateway-api.md` as *the* upgrade path off ingress-nginx, which makes the + contradiction more visible, not less. - `deploy-guide/components/ingress-nginx.md` still exists as an enabled-component page; `bootstrap/README.md` also still lists `ingress-nginx` as a bootstrap component, and lists only three of the four directories that are actually @@ -137,7 +148,11 @@ marking which pages are theirs. - `README.md` tells you to run `nix-shell`. There is no `shell.nix` or `flake.nix` in the repository. - `operator-guide/argocd-helm-chart.md` shows `understack_ref: v1.0.0 # Pin to - specific version`. There is no `v1.x` series; the newest tag is `v0.4.25`. + specific version`, in four places. There is no `v1.x` series; the newest tag is + `v0.4.28`. `docs/release-notes/index.md` now documents the same "pin a version" + step with a real tag (`understack_ref: v0.4.26`) and links to + `argocd-helm-chart.md` for the full explanation, so a reader following that + link lands on the fictional example. - Placeholders shipped in published prose: `operator-guide/openstack-ironic.md:14` reads `see [TODO: Hardware Enrollment Documentation]`; `deploy-guide/gitops-install.md:39` has an inline `(TODO: this defines the @@ -154,13 +169,19 @@ to upstream OpenStack docs), `operator-guide/openstack-placement.md` (17 lines, two commands), `operator-guide/rook-ceph.md` (24 lines, dashboard access only for a storage backend). -### The 55 component pages +### The component pages + +56 of them plus an index. Roughly 27 are 45–51 line near-identical template +output; `nautobot-worker.md` is 762 lines, about 30x the median. They are +machine-shaped reference material sitting inside the install narrative. -Roughly 27 are 45–51 line near-identical template output; `nautobot-worker.md` is -762 lines, about 30x the median. They are machine-shaped reference material -sitting inside the install narrative. The hand-maintained 55-row table in -`deploy-guide/components/index.md` is already visibly rotting: the ten OpenStack -services are appended out of alphabetical order. +The hand-maintained table in `deploy-guide/components/index.md` is already +visibly rotting. It has 55 rows for 56 pages: `nautobot-worker` has never been +listed, so the single largest component page is unreachable from the index that +exists to reach them. The ten OpenStack services are also appended out of +alphabetical order. Note that `scripts/check-component-docs.py` does not look at +this table at all — it only compares template names to filenames — so nothing +catches the omission. ### Build and CI hygiene @@ -169,11 +190,12 @@ services are appended out of alphabetical order. `extra_css` points only at `stylesheets/rackspace-theme.css`, and no `theme.custom_dir` is configured. They are the only files in `docs/overrides/`. -- `requirements-docs.txt` installs three plugins that are not enabled in +- `requirements-docs.txt` installs three mkdocs plugins that are not enabled in `plugins:` and are unused: `mkdocs-swagger-ui-tag`, `mkdocs-glightbox`, - `mkdocs-multirepo-plugin`. + `mkdocs-multirepo-plugin`. (`scriv`, added by #2191, is not one of these — it + is not a plugin and the `unreleased-notes` target calls it.) - `docs/assets/mermaid.min.js` is 2.75 MB vendored into the repository. -- `Makefile:22`'s `WFTMPLS := $(wildcard components/*-workflows/*/workflowtemplates/*.yaml)` +- `Makefile:26`'s `WFTMPLS := $(wildcard components/*-workflows/*/workflowtemplates/*.yaml)` matches **zero** files. The 15 real templates are in `workflows/argo-events/workflowtemplates/`. The target is `.PHONY` so the build still works, but the dependency tracking is vestigial. @@ -186,21 +208,22 @@ services are appended out of alphabetical order. ## Proposed layout -Six tabs. `Home` is a router and `Reference` is cross-cutting lookup; the other -four map onto the five audiences, with system operators spanning both `Deploy` -(day 0) and `Operations` (day 2). There is deliberately no "Introduction" tab: -with no evaluator audience left to sit and read one end-to-end, the short "what -this is / how it is shaped" overview belongs on `Home`, and the architecture -depth a system operator actually needs belongs in `Operations`, next to the -troubleshooting it supports. +Seven tabs. `Home` is a router and `Reference` is cross-cutting lookup; the other +five map onto the five audiences, with system operators spanning `Deploy` +(day 0), `Operations` (day 2) and `Release Notes` (upgrades). There is +deliberately no "Introduction" tab: with no evaluator audience left to sit and +read one end-to-end, the short "what this is / how it is shaped" overview belongs +on `Home`, and the architecture depth a system operator actually needs belongs in +`Operations`, next to the troubleshooting it supports. | Tab | Audience | Why | | --- | --- | --- | | **Home** | router | Already has `hide: [navigation, toc]` and a card grid. Becomes an explicit router across the five audiences, and carries the short project overview. | | **Deploy** | system operators, day 0 | A linear install narrative with a beginning and an end. | | **Operations** | system operators, DC techs, network operations, day 2 | The runbook library you land in from a pager, reorganized into role-aligned nav groups instead of one flat list — this is the fix for "Operator Guide grouping" above. | +| **Release Notes** | system operators, upgrades | Added by #2191, after this audit was first written. Kept as its own tab — see below. | | **Using the Cloud** | users (cloud tenants) | Today's `User Guide`. A tenant looking for how to drive the OpenStack CLI would never think to look under "Operations", so this audience keeps its own front door rather than being buried in an operator tab. | -| **Contributing** | contributors | Does not exist today. Front door for the 58 external files. | +| **Contributing** | contributors | Does not exist today. Front door for the 61 external files. | | **Reference** | all, lookup mode | Component pages, generated workflow docs, hardware schemas, config samples. Does not belong inside a narrative. | Within Operations the groups are role-aligned, but not everything collapses to @@ -222,6 +245,18 @@ stay put: monitoring, Ansible — get sorted across these groups by who actually reads them, rather than surviving as a sixth, uncategorized dumping ground. +`Release Notes` keeps the top-level tab it shipped with, rather than becoming an +"Upgrading" group inside Operations. Its audience is system operators, which is +the Operations audience, so the audience rule does not force the question either +way — this is a nav-placement call, and `docs/release-notes/` keeps its path +regardless. Three reasons to leave it where it is: readers arrive at it from a +version-pin decision rather than from browsing runbooks; it is the only part of +the tree with a freshness contract and CI enforcing it (`release-note-check.yaml`, +`make unreleased-notes`), which is easier to keep visible at the top level; and it +is newly built and linked, so moving it spends redirect and link-fixing budget for +no reader gain. `operator-guide/index.md` already carries an `## Upgrading` +section pointing into it, which is the cross-link that makes a separate tab work. + `User Guide` keeps its own tab as **Using the Cloud** rather than being folded into Operations. It is only five pages averaging ~110 lines (one of them a duplicate), and `operator-guide/index.md` already treats `openstack-cli.md` as a @@ -230,11 +265,14 @@ consumers, not operators, and the whole point of this redesign is that each audience gets a front door it will actually look behind. Dropping the "Introduction" tab is what pays for keeping this one. -If six tabs still feels like one too many, the pressure valve is to nest -**Using the Cloud** back under Operations — it is the thinnest tab and has the -most operator overlap. What must not give is Contributing or the per-role -grouping inside Operations: those are the front doors this whole redesign -exists to create. +Seven tabs is one more than the original six, and it is one more than is +comfortable. If tab count has to come down, spend it in this order: nest +**Using the Cloud** under Operations first — it is the thinnest tab and has the +most operator overlap — then **Release Notes** as an Operations "Upgrading" +group. What must not give is Contributing or the per-role grouping inside +Operations: those are the front doors this whole redesign exists to create. +Note that seven tabs is still a reduction in *top-level* entries from today's +eight. Also worth dropping `navigation.expand` from `theme.features`: expand-all is noise at this size. @@ -249,12 +287,29 @@ Consequences: - **`deploy-guide/` and `operator-guide/` keep their on-disk paths permanently.** They map 1:1 to the Deploy and Operations tabs, their URL prefixes are already accurate, and they are the paths that in-repo and external links reference. - Renaming them to `deploy/` and `operations/` would break - `charts/argocd-understack/values.yaml` (four places), `README.md`, - `scripts/README.md`, `go/understackctl/README.md`, - `examples/openstack-notifications/README.md` and three - `examples/*/README.md` — for zero content benefit. + Renaming them to `deploy/` and `operations/` would break seven files outside + `docs/` for zero content benefit — `charts/argocd-understack/values.yaml` (four + places) and `README.md` for `deploy-guide/`; `scripts/README.md`, + `go/understackctl/README.md`, `operators/monitoring/README.md`, + `examples/openstack-notifications/README.md` and + `ansible/roles/nova_flavors/README.md` for `operator-guide/`. + + It would also **silently disable a check**, which is worse than breaking a + link. `.pre-commit-config.yaml` scopes the Component Docs Check hook with + `files: '...|^docs/deploy-guide/components/.*\.md$'`. Rename the directory and + the hook still passes — it just stops matching anything. - `user-guide/` also keeps its path, surfaced as the **Using the Cloud** tab. + Three `examples/*/README.md` files link to `understack/user-guide/`: + `terraform-trunk-ports`, `tf-multi-node-build` and `tf-multi-node-router`. +- **`release-notes/` keeps its path, and this one is not negotiable.** + `.github/workflows/release-notes.yaml` writes + `https://rackerlabs.github.io/understack/release-notes//#changelog-` + into every GitHub release body, and a published release body is not something + we get to go back and fix. Renaming the directory 404s every past release; even + retitling a version heading breaks the `#changelog-` anchor. `Makefile`, + `.gitignore`, `changelog.d/scriv.ini`, `changelog.d/unreleased.ini`, + `RELEASING.md` and `.github/workflows/properdocs.yaml` all hardcode the path as + well. - **`design-guide/` is the only directory that dies**, because its contents genuinely split three ways. @@ -263,8 +318,9 @@ Consequences: Each phase is a separate reviewable PR. - **Phase 1 — nav rewrite plus the new front doors.** This is the phase that - delivers the front doors. Rewrite `nav:` to the six tabs using **existing - on-disk paths only** — no file moves. Add `docs/contributing/index.md`, + delivers the front doors. Rewrite `nav:` to the seven tabs using **existing + on-disk paths only** — no file moves. `Release Notes` carries over untouched, + including its hand-ordering comment. Add `docs/contributing/index.md`, `docs/reference/index.md` and a `docs/operator-guide/troubleshooting.md` hub, and rewrite the 8-line upstream-forwarding `user-guide/index.md` into a real **Using the Cloud** front door. Rewrite the "Getting Started" card on @@ -282,22 +338,59 @@ Each phase is a separate reviewable PR. - **Phase 3 — dissolve `design-guide/`.** Hardware schemas to `reference/`, the four design deep-dives to `contributing/design/`, and the architecture overview into the Operations **Architecture & Troubleshooting** group, - consolidated there with the five `component-*.md` overview blurbs. Needs the - `Makefile` edit for the generated sample config path and the two - `ansible/roles/nova_flavors/` link fixes. Add the missing reciprocal links - between the reference and how-to hardware pages. + consolidated there with the five `component-*.md` overview blurbs. Add the + missing reciprocal links between the reference and how-to hardware pages. + + This is the phase with real link work, and an earlier draft of this document + badly undercounted it as "the two `ansible/roles/nova_flavors/` link fixes". + The actual set is: + + - **12 relative `../design-guide/*.md` links across 4 pages**, 9 of them in + `operator-guide/openstack-ironic-inspection-guide.md` alone. Redirects do + not help — `validation.not_found` reads the markdown source. Phase 1's two + new front doors add 5 more, for 17 across 6 pages. + - **One** external link, `ansible/roles/nova_flavors/README.md:15`. Line 243 + of the same file points at `operator-guide/flavors/` and must be left + alone, which is how the "two fixes" miscount happened. + - `Makefile` lines 22 and 49, for the generated sample config path. - **Phase 4 — write the Contributing tab.** The largest writing effort; split per-language so each sub-PR has an owner. Add root `CONTRIBUTING.md`, which - GitHub surfaces in the PR UI. -- **Phase 5 — anti-rot CI.** + GitHub surfaces in the PR UI alongside the `pull_request_template.md` that now + exists. Surface `RELEASING.md` in this tab rather than rewriting it — it is + current, and `RELEASING.md` at the root is the path the CI failure message and + the PR template point at, so it stays where it is and the tab links to it (or + includes it via `pymdownx.snippets`, which is already enabled). +- **Phase 5 — anti-rot CI.** `.github/workflows/release-note-check.yaml` is the + shape to copy for the checks below: no `paths:` filter, short-circuit to + success, so it can actually be marked required. A path-filtered workflow never + reports a status on non-matching PRs and therefore can never be required. ### Mechanics that are easy to get wrong -- **You cannot de-list a page.** All 133 pages are in `nav:`, and +- **You cannot de-list a page.** All 137 pages are in `nav:`, and `validation.omitted_files` plus `properdocs build --strict` means any file under `docs/` missing from `nav:` fails the build. Every reorg step must either place a page somewhere in nav or delete it. This is also free orphan detection in both directions — do not write a script for it. +- **`release-notes/unreleased.md` is in `nav:` but not in git.** It is generated + by `make unreleased-notes` and gitignored, so `nav:` and the working tree only + agree after that target has run. Two consequences: the `docs` and `docs-local` + targets depend on it and `properdocs build --strict` fails without it, so never + run `properdocs build` directly during a reorg; and any tool that reconciles + `nav:` against tracked files needs this file — and the generated + `docs/workflows/` tree and neutron sample config — on an allowlist. +- **The Release Notes nav list is hand-ordered on purpose.** The comment in + `properdocs.yml` spells it out: `include_dir_to_nav` only sorts ascending + ASCII, so `v0.10.md` would land before `v0.9.md`, and its reverse toggle is + **global** — flipping it for Release Notes would also reverse the generated + Workflows section. That global-toggle constraint bounds the `docs/workflows/` + item further down this list: nesting it under Reference is safe because it does + not change the sort settings, but do not reach for `include_dir_to_nav` to tidy + any other section. +- **`MD024` is now `siblings_only: true`,** relaxed so release-notes pages can + repeat "Action required" per version. Duplicate headings are still an error + between siblings, which is exactly the case Phase 2 creates when it + concatenates two pages — so the merges still need heading passes. - **`validation` has no `error` level.** It only accepts `warn`/`info`/`ignore`, so `--strict` in the Makefile is the only thing that turns these into failures. Do not drop it. @@ -331,12 +424,17 @@ Each phase is a separate reviewable PR. reads. `_render_sources()` returns `""` on a missing key today, so the page silently renders an empty bullet list. 4. Generate the component index table from front matter via a new - `component_table()` macro instead of maintaining 55 rows by hand. + `component_table()` macro instead of maintaining 55 rows by hand. This one has + a live bug to fix, not just a hypothetical: the table is missing + `nautobot-worker`, and has been since before this audit. 5. `scripts/check-published-links.py` — assert every `rackerlabs.github.io/understack/` reference in non-`docs/` files resolves inside the built `site/`. This is the check that would have caught the `operators/monitoring/README.md` 404, and it is what makes future moves - safe. + safe. It now matters more: `.github/workflows/release-notes.yaml` constructs + such a URL and writes it into GitHub release bodies, where a 404 is permanent + because the body is already published. That workflow's URL template belongs in + this check's scope. 6. A contributor-tab coverage check: every top-level `go//` and `python//` directory must be mentioned in the Contributing tab. Same shape as `check-component-docs.py`, and the thing that stops Phase 4's work decaying @@ -344,6 +442,18 @@ Each phase is a separate reviewable PR. ## Status of the work -**Everything above is unstarted**, including Phase 1. Phases 1 through 5 -are a proposal, not a plan of record — the point of this document is to get -agreement on the target layout before anyone starts moving pages. +**Phases 1 through 5 are unstarted**, and they are a proposal rather than a plan +of record — the point of this document is to get agreement on the target layout +before anyone starts moving pages. + +One piece of the target layout does now exist, arrived at independently: #2191 +added the `Release Notes` tab, `docs/release-notes/`, the `changelog.d/` fragment +workflow, `RELEASING.md` and the CI that enforces a note on upgrade-impacting +pull requests. This document has been re-checked against it. Nothing in the +findings was invalidated; the counts moved (133 → 137 pages, 7 → 8 top-level nav +entries, 58 → 61 external markdown files), the proposal grew a seventh tab, and +three new mechanics were added — the generated-but-navigated +`release-notes/unreleased.md`, the global `include_dir_to_nav` sort constraint, +and the immutability of the release-body URLs. Two findings got *worse* evidence +in the process: the fictional `understack_ref: v1.0.0` and the stale +`config-dex.md` are both now contradicted by pages release notes links to. From ffd8a36f806ce4f7c400944f6f1efa1dc20791b6 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 18 Aug 2026 14:13:50 -0500 Subject: [PATCH 2/3] docs: rewrite nav into seven audience tabs Phase 1 of docs-layout-audit.md: give each audience a front door. Nav-only, no file moves, so no in-repo link and no published URL changes but the one deletion. Adds the missing Contributing and Reference front doors and a troubleshooting hub to land on from a pager, rewrites user-guide/index.md and the Home cards, and regroups Operations by operator role to dissolve the 17-page Infrastructure dumping ground. Deletes the design-guide/intro.md stub, redirected via mkdocs-redirects. Verified with properdocs build --strict and markdownlint. --- docs-layout-audit.md | 164 +++++++++++++------ docs/contributing/index.md | 78 +++++++++ docs/design-guide/intro.md | 3 - docs/index.md | 74 ++++++++- docs/operator-guide/hardware.md | 26 +++ docs/operator-guide/index.md | 23 ++- docs/operator-guide/networking.md | 16 ++ docs/operator-guide/troubleshooting.md | 60 +++++++ docs/reference/index.md | 51 ++++++ docs/user-guide/index.md | 52 +++++- properdocs.yml | 212 ++++++++++++++----------- requirements-docs.txt | 1 + 12 files changed, 608 insertions(+), 152 deletions(-) create mode 100644 docs/contributing/index.md delete mode 100644 docs/design-guide/intro.md create mode 100644 docs/operator-guide/hardware.md create mode 100644 docs/operator-guide/networking.md create mode 100644 docs/operator-guide/troubleshooting.md create mode 100644 docs/reference/index.md diff --git a/docs-layout-audit.md b/docs-layout-audit.md index 9ddfa980a..d66f25355 100644 --- a/docs-layout-audit.md +++ b/docs-layout-audit.md @@ -1,8 +1,8 @@ # Documentation Layout Audit -**Status:** proposal, for team review. This file is a planning artifact, not -product documentation. Delete it once the phases below have landed or been -rejected. +**Status:** Phase 1 has landed; Phases 2–6 are a proposal, for team review. This +file is a planning artifact, not product documentation. Delete it once the phases +below have landed or been rejected. It lives at the repository root rather than under `docs/` on purpose: every file under `docs/` has to be added to `nav:` in `properdocs.yml` and gets published to @@ -28,6 +28,11 @@ configuring the network — it hands all three the same flat page list. ## What is there now +The counts and structure in this section describe the layout **as it was before +Phase 1**, because that is what the findings below are about. Phase 1 has since +changed the tabs and added five pages; it moved nothing, so every finding still +points at a real file. Current state is 141 pages under seven tabs. + 137 markdown pages, all of them listed in the hand-maintained `nav:`. Eight top-level entries: `Home`, `Overview`, `Design Guide`, `Deployment Guide`, `Operator Guide`, `Release Notes`, `User Guide`, `Workflows`. @@ -49,9 +54,11 @@ at `workflows/argo-events/docs/`. ### The big one: no contributor documentation exists -There is no contributor section on the site and no `CONTRIBUTING.md` in the -repository. Everything a new developer needs is in those 61 external files, which -are unlinked, unlinted for links, and drifting. The clearest symptom is three +There was no contributor section on the site, and there is still no +`CONTRIBUTING.md` in the repository. Phase 1 added a `Contributing` front door, +but a front door is not documentation: everything a new developer actually needs +is still in those 61 external files, which are unlinked, unlinted for links, and +drifting. Phase 6 is where that gets fixed. The clearest symptom is three near-identical `DEVELOPMENT.md` files (`python/ironic-understack/`, `python/neutron-understack/`, `python/understack-workflows/`) that differ by a handful of lines — the classic copy-paste decay. @@ -169,6 +176,11 @@ to upstream OpenStack docs), `operator-guide/openstack-placement.md` (17 lines, two commands), `operator-guide/rook-ceph.md` (24 lines, dashboard access only for a storage backend). +Phase 1 dealt with the first two: `design-guide/intro.md` is deleted (redirected +to `contributing/`) and `user-guide/index.md` is a real front door. The two +`operator-guide` stubs are still stubs — they are thin because the underlying +material is thin, which is a writing problem rather than a layout one. + ### The component pages 56 of them plus an index. Roughly 27 are 45–51 line near-identical template @@ -227,23 +239,41 @@ on `Home`, and the architecture depth a system operator actually needs belongs i | **Reference** | all, lookup mode | Component pages, generated workflow docs, hardware schemas, config samples. Does not belong inside a narrative. | Within Operations the groups are role-aligned, but not everything collapses to -exactly three — two existing groups are already system-operator material and +exactly three — several existing groups are already system-operator material and stay put: -- **Architecture & Troubleshooting** (system operators): the architecture - overview and the five `component-*.md` overview blurbs, plus the new - troubleshooting hub and `troubleshooting-osh.md`, `ovs-ovn.md`, - `kubectl-us-net.md`, `logging.md`. +- **Troubleshooting and Architecture** (system operators): the new troubleshooting + hub, the architecture overview, the five `component-*.md` overview blurbs, and + the pages that are about diagnosing the deployment as a whole rather than one + subsystem — `troubleshooting-osh.md`, `logging.md`, `monitoring.md`. - **OpenStack Services** (system operators): today's per-service `OpenStack` group, largely as-is. +- **Platform Services** (system operators): the services UnderStack runs + *alongside* OpenStack — ArgoCD, Argo Workflows, Gateway API, Nautobot and its + operator, MariaDB, Postgres, RabbitMQ, Rook Ceph. - **Hardware** (DC techs): BMC/Redfish, firmware, enrollment — today's - `Hardware` section, largely as-is. + `Hardware` section, plus `bmc-password.md`. - **Networking** (network operations): today's `Networking` section. -- **Scripts and Tools** (system operators): cross-cutting, kept as-is. +- **Scripts and Tools** (system operators): cross-cutting, plus + `ansible-local-usage.md`. `Infrastructure`'s 17 pages — databases, ingress, Nautobot, logging, monitoring, Ansible — get sorted across these groups by who actually reads -them, rather than surviving as a sixth, uncategorized dumping ground. +them, rather than surviving as an uncategorized dumping ground. Most of them +land in **Platform Services**, which is a sixth group but a named and coherent +one: "the supporting services", not "everything else". + +Two notes on judgement calls this list originally got wrong: + +- `ovs-ovn.md` and `kubectl-us-net.md` are **network operations** tools, so they + stay in **Networking**. An earlier draft of this document listed them in both + Networking and the group then called "Architecture & Troubleshooting", which is + not a thing nav can do. + The troubleshooting hub links to them instead — which is the point of having a + hub: a page can be indexed from anywhere while living in exactly one place. +- The supporting services do not fit inside **OpenStack Services**, because + MariaDB and Nautobot are not OpenStack services. Widening that group's label to + cover them would have made it meaningless. `Release Notes` keeps the top-level tab it shipped with, rather than becoming an "Upgrading" group inside Operations. Its audience is system operators, which is @@ -315,29 +345,58 @@ Consequences: ### Phasing -Each phase is a separate reviewable PR. - -- **Phase 1 — nav rewrite plus the new front doors.** This is the phase that - delivers the front doors. Rewrite `nav:` to the seven tabs using **existing - on-disk paths only** — no file moves. `Release Notes` carries over untouched, - including its hand-ordering comment. Add `docs/contributing/index.md`, - `docs/reference/index.md` and a `docs/operator-guide/troubleshooting.md` hub, - and rewrite the 8-line upstream-forwarding `user-guide/index.md` into a real - **Using the Cloud** front door. Rewrite the "Getting Started" card on - `docs/index.md` into five audience cards — system operators, DC techs, network - operations, users, contributors (cheapest high-value edit in the whole plan). - Delete `design-guide/intro.md`. Add `mkdocs-redirects`. Revertable by - reverting one file, with zero external breakage. -- **Phase 2 — content merges.** Fold `secrets.md` into `secrets-eso-setup.md` +Each phase is a reviewable stream of work. Keep individual PRs small, but land +the phases in this order: correctness and guardrails precede the larger content +moves they are meant to protect. + +- **Phase 1 — nav rewrite plus the new front doors. Done.** `nav:` is now the + seven tabs, using **existing on-disk paths only** — no file moved, so no + in-repo or external link changed. `Release Notes` carried over untouched, + including its hand-ordering comment. Added `docs/contributing/index.md`, + `docs/reference/index.md`, role landing pages for Hardware and Networking, and + the `docs/operator-guide/troubleshooting.md` hub; + rewrote the 8-line upstream-forwarding `user-guide/index.md` into a real + **Using the Cloud** front door and the "Getting Started" card on `docs/index.md` + into audience cards — six of them, not five, because system operators get one + for day 0 and one for day 2 rather than being asked which they are. Deleted + `design-guide/intro.md` and added + `mkdocs-redirects` carrying its one redirect. Also dropped `navigation.expand`, + since that only made sense alongside the nav rewrite. + + Three things came up during implementation that the plan above had not + settled: + + - **`vision.md` is parked on the `Home` tab.** Phase 4 folds it into + `docs/index.md`, but a page cannot be de-listed in the meantime, so `Home` + is temporarily a two-page section rather than a single page. + - **`Platform Services` was needed as a sixth Operations group**, because the + supporting services have nowhere else to go. See the note above. + - **`kubernetes.md`, `secrets.md` and `networking.md` sit under `Deploy`** + with their current filenames. They read oddly there — `networking.md` in + particular — but renaming them is Phase 4's job, and doing it here would + have broken the "no file moves" property that makes this phase cheap to + revert. +- **Phase 2 — correctness.** Fix guidance that can send readers down a known + wrong path before reorganizing more of the site. At minimum: replace the + ingress-nginx instructions in `deploy-guide/config-dex.md`, decide whether the + `ingress-nginx` component page and bootstrap entry should be removed, replace + the fictional `understack_ref: v1.0.0` examples, resolve published TODOs on the + primary deployment path, and either add real Ceph/PostgreSQL/BMC diagnostics + or keep the troubleshooting index scoped to what those pages actually cover. +- **Phase 3 — anti-rot CI.** Land the checks listed below before moving files or + generating more indexes. Fix the missing `nautobot-worker` component-index row + and the zero-match workflow dependency glob as part of introducing the checks, + so each guardrail starts from a clean baseline. +- **Phase 4 — content merges.** Fold `secrets.md` into `secrets-eso-setup.md` and the two `server-firmware-update.md` into one. Move `networking.md` to `deploy-guide/provisioning-network.md` and `kubernetes.md` to `deploy-guide/tools.md`, which dissolves the naming collision. Fold the Overview vision statement into `docs/index.md`. Retitle the colliding H1s so the three OpenStack Helm pages become "Why We Diverge from OpenStack Helm", "openstack-helm (component)" and "Troubleshooting OpenStack Helm". -- **Phase 3 — dissolve `design-guide/`.** Hardware schemas to `reference/`, the +- **Phase 5 — dissolve `design-guide/`.** Hardware schemas to `reference/`, the four design deep-dives to `contributing/design/`, and the architecture - overview into the Operations **Architecture & Troubleshooting** group, + overview into the Operations **Troubleshooting and Architecture** group, consolidated there with the five `component-*.md` overview blurbs. Add the missing reciprocal links between the reference and how-to hardware pages. @@ -345,25 +404,28 @@ Each phase is a separate reviewable PR. badly undercounted it as "the two `ansible/roles/nova_flavors/` link fixes". The actual set is: - - **12 relative `../design-guide/*.md` links across 4 pages**, 9 of them in - `operator-guide/openstack-ironic-inspection-guide.md` alone. Redirects do - not help — `validation.not_found` reads the markdown source. Phase 1's two - new front doors add 5 more, for 17 across 6 pages. + - **17 relative `../design-guide/*.md` links across 6 pages.** + `operator-guide/openstack-ironic-inspection-guide.md` alone has 9; + `reference/index.md` has 4 and `contributing/index.md` has 1, both added by + Phase 1. Redirects do not help here — `validation.not_found` reads the + markdown source. - **One** external link, `ansible/roles/nova_flavors/README.md:15`. Line 243 of the same file points at `operator-guide/flavors/` and must be left alone, which is how the "two fixes" miscount happened. - `Makefile` lines 22 and 49, for the generated sample config path. -- **Phase 4 — write the Contributing tab.** The largest writing effort; split +- **Phase 6 — write the Contributing tab.** The largest writing effort; split per-language so each sub-PR has an owner. Add root `CONTRIBUTING.md`, which GitHub surfaces in the PR UI alongside the `pull_request_template.md` that now exists. Surface `RELEASING.md` in this tab rather than rewriting it — it is current, and `RELEASING.md` at the root is the path the CI failure message and the PR template point at, so it stays where it is and the tab links to it (or includes it via `pymdownx.snippets`, which is already enabled). -- **Phase 5 — anti-rot CI.** `.github/workflows/release-note-check.yaml` is the - shape to copy for the checks below: no `paths:` filter, short-circuit to - success, so it can actually be marked required. A path-filtered workflow never - reports a status on non-matching PRs and therefore can never be required. + +Every PR that moves or merges a page must include its redirect map, update all +in-repository links, and pass `make docs`. Before merging a phase, also verify +the old published URLs in the built `site/`, inspect merged pages for duplicate +headings, and confirm the new landing pages still route every advertised task to +content that performs that task. ### Mechanics that are easy to get wrong @@ -389,7 +451,7 @@ Each phase is a separate reviewable PR. any other section. - **`MD024` is now `siblings_only: true`,** relaxed so release-notes pages can repeat "Action required" per version. Duplicate headings are still an error - between siblings, which is exactly the case Phase 2 creates when it + between siblings, which is exactly the case Phase 4 creates when it concatenates two pages — so the merges still need heading passes. - **`validation` has no `error` level.** It only accepts `warn`/`info`/`ignore`, so `--strict` in the Makefile is the only thing that turns these into failures. @@ -412,7 +474,12 @@ Each phase is a separate reviewable PR. Reference with no Makefile change — and a generated tree cannot have explicit per-page redirect keys anyway. -### Anti-rot checks worth adding (Phase 5) +### Anti-rot checks worth adding (Phase 3) + +Use `.github/workflows/release-note-check.yaml` as the workflow shape: no +`paths:` filter and a short-circuit to success, so the check can be required. A +path-filtered workflow never reports a status on non-matching pull requests and +therefore cannot be required. 1. Make `check-component-docs.py` **bidirectional**, so a deleted template does not leave a zombie page. Requires allowlisting the eleven pages that have no @@ -437,14 +504,21 @@ Each phase is a separate reviewable PR. this check's scope. 6. A contributor-tab coverage check: every top-level `go//` and `python//` directory must be mentioned in the Contributing tab. Same shape as - `check-component-docs.py`, and the thing that stops Phase 4's work decaying + `check-component-docs.py`, and the thing that stops Phase 6's work decaying back into invisible files. ## Status of the work -**Phases 1 through 5 are unstarted**, and they are a proposal rather than a plan -of record — the point of this document is to get agreement on the target layout -before anyone starts moving pages. +**Phase 1 is done.** The seven tabs, five new front doors and the troubleshooting +hub are in place; `properdocs build --strict` is clean and +`check-component-docs.py` passes. Nothing moved on disk, so nothing outside +`properdocs.yml` and the pages listed in that phase changed. + +**Phases 2 through 6 are unstarted**, and they remain a proposal rather than a +plan of record. Phase 1 was deliberately the phase that commits to nothing: the +findings below still need agreement before pages start moving, and the fastest +way to disagree with the target layout is now to click around the seven tabs and +say what feels wrong. One piece of the target layout does now exist, arrived at independently: #2191 added the `Release Notes` tab, `docs/release-notes/`, the `changelog.d/` fragment diff --git a/docs/contributing/index.md b/docs/contributing/index.md new file mode 100644 index 000000000..f61767678 --- /dev/null +++ b/docs/contributing/index.md @@ -0,0 +1,78 @@ +# Contributing + +This section is for people changing UnderStack itself, rather than deploying or +operating it. + +!!! note "This front door is new and incomplete" + Most contributor documentation still lives in `README.md` and + `DEVELOPMENT.md` files next to the code. This page links to the main entry + points until that material is consolidated here. + +## Start here + +- [RELEASING.md](https://github.com/rackerlabs/understack/blob/main/RELEASING.md) + — how release notes and tags work. Read this before opening a pull request + that changes anything an operator has to react to: such a pull request needs a + `changelog.d/` fragment, and CI enforces it. +- [Adding and Removing an Application](../design-guide/add-remove-app.md) — how a + component becomes an ArgoCD `Application`. +- [Design and Background](../openstack-helm.md) — why the project is shaped the + way it is, starting with why we diverge from upstream OpenStack Helm. + +## Development environments + +Each language and package keeps its own setup instructions with the code. These +links open the current version on GitHub: + +| Area | Entry points | +| --- | --- | +| Python packages | [Ironic][py-ironic], [Neutron][py-neutron], [Nova][py-nova], [workflows][py-workflows], [OpenStack sync][py-sync] | +| Go operators and CLIs | [understackctl][go-understackctl], [dexop][go-dexop], [nautobotop][go-nautobotop], [Ironic hardware exporter][go-ihe] | +| Helm charts | [ArgoCD UnderStack chart][chart-argocd], [site workflows][chart-workflows] | +| Ansible | [Playbooks and roles][ansible] | +| Containers | [Ironic][container-ironic], [Nautobot][container-nautobot], [Neutron][container-neutron], [Nova][container-nova] | +| End-to-end tests | [understack-tests][tests] | + +Python packages use [uv](https://docs.astral.sh/uv/) with `pytest` and `ruff`; +Go projects use a `Makefile` with `golangci-lint`. Run the checks for the area +you touched before opening a pull request. + +## Documentation + +The site is built with [properdocs](https://github.com/rackerlabs/properdocs) +from `properdocs.yml`: + +```bash +make docs-local # build and serve on http://127.0.0.1:8001 +make docs # build with --strict, as CI does +``` + +Two things to know before you add a page: + +- **Every page under `docs/` must appear in `nav:`.** `validation.omitted_files` + plus `--strict` makes an unlisted page a build failure. There is no way to ship + a page that is not in the navigation, which is deliberate — it is what keeps + orphans out. +- **Some pages are generated**, and are gitignored rather than committed: + `docs/workflows/` (from the Argo templates), the neutron sample config, and + `docs/release-notes/unreleased.md` (from `changelog.d/`). Use `make docs` + rather than calling `properdocs build` directly, or the generated pages will be + missing and `--strict` will fail. + +[py-ironic]: https://github.com/rackerlabs/understack/blob/main/python/ironic-understack/README.md +[py-neutron]: https://github.com/rackerlabs/understack/blob/main/python/neutron-understack/DEVELOPMENT.md +[py-nova]: https://github.com/rackerlabs/understack/blob/main/python/nova-understack/README.md +[py-workflows]: https://github.com/rackerlabs/understack/blob/main/python/understack-workflows/README.md +[py-sync]: https://github.com/rackerlabs/understack/blob/main/python/openstack-sync/README.md +[go-understackctl]: https://github.com/rackerlabs/understack/blob/main/go/understackctl/README.md +[go-dexop]: https://github.com/rackerlabs/understack/blob/main/go/dexop/README.md +[go-nautobotop]: https://github.com/rackerlabs/understack/blob/main/go/nautobotop/README.md +[go-ihe]: https://github.com/rackerlabs/understack/blob/main/go/ironic-hardware-exporter/README.md +[chart-argocd]: https://github.com/rackerlabs/understack/blob/main/charts/argocd-understack/README.md +[chart-workflows]: https://github.com/rackerlabs/understack/blob/main/charts/site-workflows/README.md +[ansible]: https://github.com/rackerlabs/understack/blob/main/ansible/README.md +[container-ironic]: https://github.com/rackerlabs/understack/blob/main/containers/ironic/README.md +[container-nautobot]: https://github.com/rackerlabs/understack/blob/main/containers/nautobot/README.md +[container-neutron]: https://github.com/rackerlabs/understack/blob/main/containers/neutron/README.md +[container-nova]: https://github.com/rackerlabs/understack/blob/main/containers/nova/README.md +[tests]: https://github.com/rackerlabs/understack/blob/main/python/understack-tests/README.md diff --git a/docs/design-guide/intro.md b/docs/design-guide/intro.md deleted file mode 100644 index ac40a43e1..000000000 --- a/docs/design-guide/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Introduction - -This section aims to provide developer focused design and code documentation. diff --git a/docs/index.md b/docs/index.md index c071791ad..86a28be07 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,12 +32,76 @@ hide: - OVN networking - Prometheus monitoring and metrics stack -- :material-abacus:{ .lg .middle } __Getting Started__ +- :material-map-marker-path:{ .lg .middle } __How this site is arranged__ - The documentation covers both Kubernetes cluster deployment options and configuration - of the UnderStack components for bare metal resource management in a multi-data center - environment. + Six starting points for five audiences, plus + [__Reference__](reference/index.md) for lookup. Pick the card below that + describes what you are doing today — most people need one section and can + ignore the rest. - See our [Deploy Guide](deploy-guide/welcome.md) to begin your own deployment. + Longer background on the project's goals is in [Vision](vision.md). + + + +## Where do I start? + + + +
+- :material-server-network:{ .lg .middle } __I am standing up a cloud__ + + --- + + System operator, day 0. A linear install narrative from requirements through + to a running site cluster. + + [:octicons-arrow-right-24: Deploy](deploy-guide/welcome.md) + +- :material-lifebuoy:{ .lg .middle } __I am running a cloud__ + + --- + + System operator, day 2. Runbooks per service, and a troubleshooting index to + land on when something is broken. + + [:octicons-arrow-right-24: Operations](operator-guide/index.md) + + [:octicons-arrow-right-24: Troubleshooting](operator-guide/troubleshooting.md) + +- :material-screwdriver:{ .lg .middle } __I am working on a machine__ + + --- + + Data centre technician. Enrolling hardware, device types, firmware and BMC + access for an individual server. + + [:octicons-arrow-right-24: Hardware](operator-guide/hardware.md) + +- :material-lan:{ .lg .middle } __I am working on the network__ + + --- + + Network operations. Neutron, OVN and Open vSwitch as they are deployed here, + and the tenant networking model behind them. + + [:octicons-arrow-right-24: Networking](operator-guide/networking.md) + +- :material-console:{ .lg .middle } __I am using the cloud__ + + --- + + Cloud tenant. Driving the OpenStack CLI and APIs to get bare metal servers, + images and networks. + + [:octicons-arrow-right-24: Using the Cloud](user-guide/index.md) + +- :material-source-branch:{ .lg .middle } __I am changing UnderStack__ + + --- + + Contributor. Development environments, how a component becomes an ArgoCD + `Application`, and how release notes work. + + [:octicons-arrow-right-24: Contributing](contributing/index.md)
diff --git a/docs/operator-guide/hardware.md b/docs/operator-guide/hardware.md new file mode 100644 index 000000000..28a39dcbd --- /dev/null +++ b/docs/operator-guide/hardware.md @@ -0,0 +1,26 @@ +# Hardware Operations + +This section is for data centre technicians and operators working with an +individual physical machine. Start with the task you need to perform: + +- [Device Type Management](device-types.md) — define and validate the hardware + models supported by a deployment. +- [Flavor Management](flavors.md) — map resource classes and hardware traits to + Nova flavors. +- [Ironic](openstack-ironic.md) — inspect or manually create bare metal nodes + and ports. +- [Ironic Inspection Guide](openstack-ironic-inspection-guide.md) — diagnose + hardware inspection failures. +- [Change Boot Interface](openstack-ironic-change-boot-interface.md) — change + how a node boots for provisioning. +- [Baremetal Box Cleanup Runbook](baremetal-ironic-cleanup-runbook.md) — recover + a node stuck in a failed provisioning state. +- [Ironic Console](openstack-ironic-console.md) — access a node's serial + console. +- [Server Firmware Updates](server-firmware-update.md) — update firmware from + the operator side. +- [BMC Password](bmc-password.md) — retrieve the generated password for a + server's management controller. + +Schema definitions for device types, traits, and flavors live under +[Reference](../reference/index.md#hardware-definitions). diff --git a/docs/operator-guide/index.md b/docs/operator-guide/index.md index 7127a58af..ad8e9496e 100644 --- a/docs/operator-guide/index.md +++ b/docs/operator-guide/index.md @@ -1,6 +1,18 @@ -# Operator Guide +# Operations -This section aims to help users who have to support a running Understack. +This section is for people supporting a running UnderStack. It covers three +roles, and the navigation is grouped by which one you are in today: + +- **System operators** supporting the deployment as a whole — *Troubleshooting + and Architecture*, *OpenStack Services*, *Platform Services*, *Scripts and + Tools*. +- **Data centre technicians** working on an individual machine — + [Hardware](hardware.md). +- **Network operations** working on network configuration — + [Networking](networking.md). + +If something is broken right now, start at +[Troubleshooting](troubleshooting.md). ## Authenticating @@ -39,8 +51,13 @@ be the regular project area. deployment between versions. If you deploy from `main`, read the [Unreleased](../release-notes/unreleased.md) page. -## Infrastructure Topics +## Platform Services + +The services UnderStack runs alongside OpenStack. Full list in the navigation; +these are the ones people look for first: +- [ArgoCD Application Management](argocd-helm-chart.md) - Enabling components and + pinning versions per cluster - [Gateway API Migration Guide](gateway-api.md) - Migration from ingress-nginx to Kubernetes Gateway API with Envoy Gateway - [Argo Workflows](workflows.md) - Workflow orchestration and troubleshooting - [OpenStack to Nautobot Sync](openstack-nautobot-sync.md) - Event-driven sync and bulk resync operations diff --git a/docs/operator-guide/networking.md b/docs/operator-guide/networking.md new file mode 100644 index 000000000..5757b098a --- /dev/null +++ b/docs/operator-guide/networking.md @@ -0,0 +1,16 @@ +# Network Operations + +This section is for network operations and system operators working with +UnderStack's control plane or tenant data plane: + +- [Neutron](openstack-neutron.md) — configure router flavors, service profiles, + and VNI allocation. +- [OVN / Open vSwitch](ovs-ovn.md) — diagnose agents, chassis, logical routers, + and traffic flow. +- [kubectl-us-net](kubectl-us-net.md) — inspect OpenStack and OVN objects through + a single troubleshooting CLI. +- [Neutron Networking Design](../design-guide/neutron-networking.md) — understand + the tenant networking and fabric model behind the operational procedures. + +For failures that may not be network-specific, start from the broader +[Troubleshooting index](troubleshooting.md). diff --git a/docs/operator-guide/troubleshooting.md b/docs/operator-guide/troubleshooting.md new file mode 100644 index 000000000..dac01a322 --- /dev/null +++ b/docs/operator-guide/troubleshooting.md @@ -0,0 +1,60 @@ +# Troubleshooting + +Somewhere to land from a pager. Troubleshooting material is spread across this +site by subsystem; this page is the index into it. + +!!! tip "If you deploy from `main`" + Before anything else, check the + [Unreleased release notes](../release-notes/unreleased.md). A deployment + tracking `HEAD` picks up changes continuously, and a change that needed + operator action is the most likely explanation for something that worked + yesterday. + +## Start from the symptom + +| Symptom | Go to | +| --- | --- | +| ArgoCD `Application` stuck syncing, or a sync hook job looping | [Deployment Troubleshooting](../deploy-guide/troubleshooting.md) | +| An OpenStack service will not start, or a chart will not render | [Troubleshooting OpenStack Helm](troubleshooting-osh.md) | +| `NeutronAgentDown`, or tenant traffic not passing | [OVN / Open vSwitch](ovs-ovn.md) | +| A router port is bound to the wrong chassis | [OVN / Open vSwitch](ovs-ovn.md#verifying-a-router-port-is-bound-to-an-ha_chassis_group) | +| A baremetal node is stuck in `clean wait`, `deleting` or `error` | [Baremetal Box Cleanup Runbook](baremetal-ironic-cleanup-runbook.md) | +| Inspection is failing or returning nothing | [Ironic Inspection Guide](openstack-ironic-inspection-guide.md) | +| A node will not PXE boot, or boots the wrong way | [Change Boot Interface](openstack-ironic-change-boot-interface.md) | +| A workflow failed, or a sensor is not firing | [Argo Workflows](workflows.md#troubleshooting) | +| Nautobot and OpenStack disagree about a resource | [OpenStack to Nautobot Sync](openstack-nautobot-sync.md) | +| You need to back up or restore an OpenStack MariaDB database | [MariaDB Operator](mariadb-operator.md) | +| You need to inspect or back up the Nautobot PostgreSQL database | [Postgres Operator](postgres-operator.md) | +| A service cannot reach RabbitMQ | [RabbitMQ](rabbitmq.md) | +| You need access to the Ceph dashboard | [Rook Ceph](rook-ceph.md) | +| A URL 404s, or TLS is wrong on an endpoint | [Gateway API](gateway-api.md) | +| An mTLS client is being rejected by Nautobot | [Nautobot mTLS Certificate Renewal](nautobot-mtls-certificate-renewal.md) | +| You need the generated password for a server's BMC | [BMC Password](bmc-password.md) | + +## Working out what happened + +- [OpenStack Logging](logging.md) — how to read an OpenStack log line, which is + the difference between a request id you can trace and a wall of text. +- [Monitoring Stack](monitoring.md) — reaching Prometheus and AlertManager, and + what the shipped alerts mean. +- [kubectl-us-net](kubectl-us-net.md) — inspecting UnderStack networking objects + from `kubectl` rather than through the OpenStack API, which is what you want + when the API itself is the thing that is broken. + +## Before you escalate + +Have these ready, because they are the first things you will be asked for: + +1. The cluster, and whether it is a global or site cluster. +2. The deployed revision — the `understack_ref` in your deploy repository, and + the commit ArgoCD actually has synced. These are not always the same. +3. The failing resource's identifier: node UUID, port id, `Application` name, or + workflow name. +4. Whether it ever worked, and what changed if so. + +## Something missing here? + +This page is only as good as its coverage. If you worked out a failure mode that +is not listed, add the row — and if the page it should point at does not exist, +that is worth saying too. See +[Contributing](../contributing/index.md). diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 000000000..e1865b7b3 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,51 @@ +# Reference + +Lookup material rather than reading material. Nothing here is a narrative — if +you are trying to *do* something, start from [Deploy](../deploy-guide/welcome.md) +or [Operations](../operator-guide/index.md) instead and let it send you here. + +## Hardware definitions + +The schema that hardware is described with, shared by Nautobot and Ironic: + +- [Device Types](../design-guide/device-types.md) — the device type schema. +- [Hardware Traits](../design-guide/hardware-traits.md) — how traits are derived + from hardware. +- [Flavors](../design-guide/flavors.md) — how flavors match hardware. + +These are the *definitions*. For the operator procedures that use them, see +[Device Types](../operator-guide/device-types.md) and +[Flavors](../operator-guide/flavors.md) under Operations. + +## Components + +A **component** is an ArgoCD `Application` generated by +`charts/argocd-understack`. One page per component, listing its sources, scope +and values: + +- [Component Reference](../deploy-guide/components/index.md) — the index, with + every component and whether it deploys to a global or site cluster. + +## Configuration samples + +- [neutron-understack Sample Configuration](../design-guide/neutron-understack-config-sample.md) + — generated from the driver's own option definitions, so it is always current + for the checked-out revision. + +## Workflow templates + +The **Workflow Templates** group in the navigation is generated at build time +from the Argo `WorkflowTemplate` definitions in `workflows/` and +`components/global-workflows/` — one page per template, showing its parameters +and the steps it runs. [Enroll Server](../workflows/enroll-server.md) is the one +to read first, since server enrolment drives most of the others. + +Because those pages are generated, they are not committed. Edit the +`WorkflowTemplate` YAML and re-run `make wftmpls`; do not edit the pages. + +## Elsewhere + +- [Release Notes](../release-notes/index.md) — what changed between versions, and + what an operator has to do about it. +- JSON schemas for deploy-repository files are published at `/schema/`, and are + referenced directly by `$schema=` URLs in `examples/deploy-repo/`. diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index 52cec1fd8..9aac2b54b 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -1,8 +1,50 @@ -# Overview +# Using the Cloud -This section aims to show you how to install the OpenStack Client -Command Line Tools (CLI) along with some basic commands -that maybe helpful. For a more complete set of documentation -see the [OpenStack Client upstream][osc] docs. +This section is for people **consuming** an UnderStack cloud: driving the +OpenStack APIs and CLI to get bare metal servers, images and networks. If you are +deploying or operating the cloud itself, you want +[Deploy](../deploy-guide/welcome.md) or +[Operations](../operator-guide/index.md) instead. + +UnderStack is an OpenStack cloud, so upstream OpenStack documentation applies +directly. The pages here cover the parts that are specific to UnderStack — +mainly that the compute you get is a real machine rather than a virtual one. + +## Set up your client + +- [OpenStack CLI](openstack-cli.md) — installing the client, configuring + `clouds.yaml` for single sign-on, and creating application credentials for + Terraform and Ansible. **Start here**; the rest of this section assumes a + working CLI. + +## Working with servers + +- [Operating System Images](openstack-image.md) — listing the available images, + adding your own, and the image properties that matter for bare metal. +- [Graphical Console](openstack-console.md) — reaching a server's console when + SSH is not an option, such as during boot or after a network change locked you + out. +- [Server Firmware Updates](server-firmware-update.md) — requesting a firmware + update on a server you hold, via node traits and runbooks. + +## Automating + +- [OpenStack Resource Controller (ORC)](openstack-resource-controller.md) — + managing OpenStack resources as Kubernetes objects, if you would rather + reconcile than script. + +For Terraform, the +[OpenStack provider](https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs) +works against UnderStack unchanged; `examples/` in the repository has working +configurations for multi-node builds, routers and trunk ports. + +## Upstream documentation + +Anything not specific to UnderStack is covered upstream: + +- [OpenStack Client][osc] — the full command reference. +- [Bare Metal service (Ironic) user guide](https://docs.openstack.org/ironic/latest/user/index.html) + — how bare metal provisioning differs from virtual machines. +- [Networking (Neutron) user guide](https://docs.openstack.org/neutron/latest/user/index.html) [osc]: diff --git a/properdocs.yml b/properdocs.yml index 99da48277..a8c33936a 100644 --- a/properdocs.yml +++ b/properdocs.yml @@ -28,7 +28,8 @@ theme: - content.code.annotate - content.code.copy - content.tooltips - - navigation.expand + # navigation.expand is deliberately absent: with seven tabs and 139 pages, + # expand-all is noise. Let readers open the group they came for. - navigation.indexes - navigation.instant - navigation.instant.progress @@ -97,6 +98,18 @@ plugins: module_name: docs/macros - callouts - mermaid-zoom + - redirects: + # A redirect_maps key must NOT be a real file under docs/, and its value + # MUST be a page in the build, or `properdocs build --strict` fails. So a + # redirect always lands in the same commit as the removal or move it + # covers, never ahead of it. + # + # These only help external inbound traffic. They do not satisfy + # validation.not_found, which reads the markdown source rather than the + # built site, so in-repo links still have to be updated for real. + redirect_maps: + # was a three-line stub; the Contributing front door replaces it + design-guide/intro.md: contributing/index.md # These only accept warn/info/ignore -- there is no "error" level -- so the # `--strict` flag in the Makefile is what actually turns them into build @@ -114,61 +127,130 @@ extra: auth_url: !ENV [OS_AUTH_URL, "https://your.endpoint.url/v3"] region_name: !ENV [OS_REGION_NAME, "my-site"] +# Seven tabs, one per audience, plus Home as a router and Reference for lookup. +# See docs-layout-audit.md for why, and for the rule that governs moves: tab +# labels and grouping here are free to change, but a file only moves on disk when +# its audience changes. Phase 1 is nav-only -- every path below is where the file +# already lives. nav: - - Home: index.md - - Overview: + - Home: + - index.md - vision.md - - 'Prerequisites': - - kubernetes.md - - openstack-helm.md - - 'Configuration': - - secrets.md - - networking.md - - Components: - - component-overview.md - - Networking: - - component-networking-neutron.md - - component-argo-workflows.md - - component-understack-workflows.md - - component-ansible.md - - 'Design Guide': - - design-guide/intro.md - - design-guide/architecture.md - - design-guide/add-remove-app.md - - 'Hardware Definitions': - - design-guide/device-types.md - - design-guide/hardware-traits.md - - design-guide/flavors.md - - design-guide/ironic.md - - design-guide/neutron-networking.md - - design-guide/neutron-understack-config-sample.md - - design-guide/argo-workflows.md - - design-guide/argo-events.md - - 'Deployment Guide': + - Deploy: - deploy-guide/welcome.md - deploy-guide/requirements.md - Quick Start: deploy-guide/gitops-install.md - - Preparing Our Deployment: + - 'Tools and Prerequisites': + - kubernetes.md + - 'Preparing Our Deployment': - deploy-guide/deploy-repo.md - deploy-guide/config-dex.md - deploy-guide/config-openstack.md - deploy-guide/secrets-eso-setup.md + - secrets.md - deploy-guide/openstack-automation-users.md - deploy-guide/nautobot-secrets.md - deploy-guide/auth.md - deploy-guide/config-argo-workflows.md - - Starting the Deployment: + - networking.md + - 'Starting the Deployment': - deploy-guide/management-cluster.md - deploy-guide/global-cluster.md - deploy-guide/site-cluster.md - - Post Deployment: + - 'Post Deployment': - deploy-guide/load-balancers.md - deploy-guide/testing-verification.md - deploy-guide/troubleshooting.md - - Further Actions: + - 'Further Actions': - deploy-guide/extra-sites.md - deploy-guide/override-openstack-svc-config.md - - Component Reference: + # Grouped by which operator role reads them: system operators get + # Troubleshooting and Architecture, OpenStack Services, Platform Services and + # Scripts and Tools; DC techs get Hardware; network operations get Networking. + - Operations: + - operator-guide/index.md + - 'Troubleshooting and Architecture': + - operator-guide/troubleshooting.md + - design-guide/architecture.md + - component-overview.md + - component-argo-workflows.md + - component-understack-workflows.md + - component-networking-neutron.md + - component-ansible.md + - operator-guide/troubleshooting-osh.md + - operator-guide/logging.md + - operator-guide/monitoring.md + - 'OpenStack Services': + - operator-guide/openstack-ironic.md + - operator-guide/openstack-ironic-inspection-guide.md + - operator-guide/openstack-ironic-change-boot-interface.md + - operator-guide/baremetal-ironic-cleanup-runbook.md + - operator-guide/openstack-ironic-console.md + - operator-guide/openstack-placement.md + - 'Networking': + - operator-guide/networking.md + - operator-guide/openstack-neutron.md + - operator-guide/ovs-ovn.md + - operator-guide/kubectl-us-net.md + - 'Hardware': + - operator-guide/hardware.md + - operator-guide/device-types.md + - operator-guide/flavors.md + - operator-guide/server-firmware-update.md + - operator-guide/bmc-password.md + - 'Platform Services': + - operator-guide/argocd-helm-chart.md + - operator-guide/workflows.md + - operator-guide/gateway-api.md + - operator-guide/nautobot.md + - operator-guide/nautobotop.md + - operator-guide/nautobot-mtls-certificate-renewal.md + - operator-guide/openstack-nautobot-sync.md + - operator-guide/mariadb-operator.md + - operator-guide/mariadb-upgrade-runbook.md + - operator-guide/postgres-operator.md + - operator-guide/rabbitmq.md + - operator-guide/rook-ceph.md + - 'Scripts and Tools': + - operator-guide/scripts.md + - operator-guide/understackctl.md + - operator-guide/ansible-local-usage.md + # Keep this list newest-series-first and maintain it by hand. Do not switch it + # to include_dir_to_nav: that plugin only does an ascending ASCII sort, so + # v0.10.md would sort before v0.9.md, and its reverse toggle is global and + # would also flip the generated Workflows section. + - 'Release Notes': + - release-notes/index.md + # generated by `make unreleased-notes` from changelog.d/, gitignored + - release-notes/unreleased.md + - release-notes/v0.5.md + - release-notes/v0.4.md + - 'Using the Cloud': + - user-guide/index.md + - user-guide/openstack-cli.md + - user-guide/openstack-image.md + - user-guide/openstack-resource-controller.md + - user-guide/openstack-console.md + - user-guide/server-firmware-update.md + - Contributing: + - contributing/index.md + - design-guide/add-remove-app.md + - 'Design and Background': + - openstack-helm.md + - design-guide/ironic.md + - design-guide/neutron-networking.md + - design-guide/argo-workflows.md + - design-guide/argo-events.md + - Reference: + - reference/index.md + - 'Hardware Definitions': + - design-guide/device-types.md + - design-guide/hardware-traits.md + - design-guide/flavors.md + - 'Configuration Samples': + # generated by `make docs` via oslo-config-generator, gitignored + - design-guide/neutron-understack-config-sample.md + - 'Components': - deploy-guide/components/index.md - deploy-guide/components/argo-events-workflows.md - deploy-guide/components/argo-events.md @@ -226,59 +308,7 @@ nav: - deploy-guide/components/skyline.md - deploy-guide/components/snmp-exporter.md - deploy-guide/components/undersync.md - - 'Operator Guide': - - operator-guide/index.md - - 'OpenStack': - - operator-guide/openstack-ironic.md - - operator-guide/openstack-ironic-inspection-guide.md - - operator-guide/openstack-ironic-change-boot-interface.md - - operator-guide/baremetal-ironic-cleanup-runbook.md - - operator-guide/openstack-ironic-console.md - - operator-guide/openstack-placement.md - - 'Networking': - - operator-guide/openstack-neutron.md - - operator-guide/ovs-ovn.md - - operator-guide/kubectl-us-net.md - - 'Infrastructure': - - operator-guide/argocd-helm-chart.md - - operator-guide/workflows.md - - operator-guide/openstack-nautobot-sync.md - - operator-guide/monitoring.md - - operator-guide/gateway-api.md - - operator-guide/bmc-password.md - - operator-guide/mariadb-operator.md - - operator-guide/mariadb-upgrade-runbook.md - - operator-guide/postgres-operator.md - - operator-guide/rabbitmq.md - - operator-guide/rook-ceph.md - - operator-guide/nautobot.md - - operator-guide/nautobotop.md - - operator-guide/nautobot-mtls-certificate-renewal.md - - operator-guide/troubleshooting-osh.md - - operator-guide/logging.md - - operator-guide/ansible-local-usage.md - - 'Hardware': - - operator-guide/device-types.md - - operator-guide/flavors.md - - operator-guide/server-firmware-update.md - - 'Scripts and Tools': - - operator-guide/scripts.md - - operator-guide/understackctl.md - # Keep this list newest-series-first and maintain it by hand. Do not switch it - # to include_dir_to_nav: that plugin only does an ascending ASCII sort, so - # v0.10.md would sort before v0.9.md, and its reverse toggle is global and - # would also flip the generated Workflows section. - - 'Release Notes': - - release-notes/index.md - # generated by `make unreleased-notes` from changelog.d/, gitignored - - release-notes/unreleased.md - - release-notes/v0.5.md - - release-notes/v0.4.md - - 'User Guide': - - user-guide/index.md - - user-guide/openstack-cli.md - - user-guide/openstack-image.md - - user-guide/openstack-resource-controller.md - - user-guide/openstack-console.md - - user-guide/server-firmware-update.md - - Workflows: workflows/ + # generated by `make wftmpls`, gitignored. include_dir_to_nav recurses into + # nested nav lists, so nesting the generated tree here needs no Makefile + # change. + - 'Workflow Templates': workflows/ diff --git a/requirements-docs.txt b/requirements-docs.txt index 0e91aa543..ef07c8799 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -8,4 +8,5 @@ mkdocs-multirepo-plugin>=0.8.3 mkdocs-include-dir-to-nav mkdocs-callouts mkdocs-mermaid-zoom +mkdocs-redirects>=1.2.3 scriv>=1.8.0 From 6ba932e92dbd3fc194e6af2de5877466e418c498 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Fri, 4 Sep 2026 15:18:26 -0500 Subject: [PATCH 3/3] docs: add contributor guide for writing Ironic drivers and interfaces Contributors adding hardware support to ironic-understack currently have no Ironic-specific onboarding: the hardware-type/interface model, the provisioning state machine, and the mixin pattern the existing drivers use are undocumented outside upstream OpenStack docs and the source itself. Cover that ground, with extra depth on writing an inspect/ management interface for devices that have no BMC or deploy ramdisk (e.g. network gear), since that's the next class of driver expected. --- docs/contributing/index.md | 3 + docs/contributing/writing-ironic-drivers.md | 309 ++++++++++++++++++++ docs/design-guide/ironic.md | 4 + properdocs.yml | 1 + 4 files changed, 317 insertions(+) create mode 100644 docs/contributing/writing-ironic-drivers.md diff --git a/docs/contributing/index.md b/docs/contributing/index.md index f61767678..83b7d87c3 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -16,6 +16,9 @@ operating it. `changelog.d/` fragment, and CI enforces it. - [Adding and Removing an Application](../design-guide/add-remove-app.md) — how a component becomes an ArgoCD `Application`. +- [Writing Ironic Drivers and Interfaces](writing-ironic-drivers.md) — how to add + a new hardware type or interface to `ironic-understack`, starting from no + Ironic knowledge. - [Design and Background](../openstack-helm.md) — why the project is shaped the way it is, starting with why we diverge from upstream OpenStack Helm. diff --git a/docs/contributing/writing-ironic-drivers.md b/docs/contributing/writing-ironic-drivers.md new file mode 100644 index 000000000..2b30744d6 --- /dev/null +++ b/docs/contributing/writing-ironic-drivers.md @@ -0,0 +1,309 @@ +# Writing Ironic Drivers and Interfaces + +This page is for contributors adding or changing bare metal behavior in +`ironic-understack`, UnderStack's Ironic plugin package. It assumes no prior +Ironic knowledge. If you already know Ironic well, skip to +[How UnderStack plugs in](#how-understack-plugs-in). + +## Ironic in a nutshell + +Ironic manages physical servers ("nodes") the way Nova manages virtual +machines. A node's behavior is defined by a **driver**, and a driver is built +from two things: + +- A **hardware type** — a Python class that says which vendor/family of + hardware this is (generic Redfish, iDRAC, a network switch, ...). +- A set of **hardware interfaces** — one implementation per concern: `power`, + `management`, `boot`, `deploy`, `inspect`, `raid`, `bios`, `network`, + `storage`, `rescue`, `firmware`, `vendor`, `console`. Each interface answers + one question, e.g. "how do I power this on?" or "how do I find out what + hardware this is?" + +Both are loaded via Python entry points and enabled by name in `ironic.conf` +(`enabled_hardware_types`, `enabled__interfaces`). This is what lets +UnderStack ship its own hardware types and interfaces as a separate package +without forking Ironic itself. + +Read these two upstream pages before writing any code — this page does not +repeat their content: + +- [Drivers](https://docs.openstack.org/ironic/latest/admin/drivers.html) — the + catalog of hardware types and interfaces Ironic ships, and how they combine. +- [Features](https://docs.openstack.org/ironic/latest/admin/features.html) — + what Ironic can do end to end (deployment, cleaning, RAID, rescue, etc.). +- [Pluggable Drivers](https://docs.openstack.org/ironic/latest/contributor/drivers.html) + — upstream's own guide to subclassing a hardware type or interface. Everything + it says about `AbstractHardwareType`, `PowerInterface`, + `ManagementInterface`, etc. applies unchanged inside `ironic-understack`. + +## The node state machine + +A node's lifecycle is a state machine — `available`, `deploying`, `active`, +`cleaning`, `manageable`, and so on — documented in full at +[Bare Metal State Machine](https://docs.openstack.org/ironic/latest/user/states.html). +Interfaces and hooks are the code that runs *during* specific state +transitions, so knowing which transition you're targeting tells you which +interface to touch. + +### The four operations we care about + +Most of the work UnderStack contributors do lands in one of four operations: + +1. **Inspecting** — discovering what a node actually is: CPU, memory, disks, + NICs, chassis model. Driven by the `inspect` interface + (`inspect_hardware()`), plus an `ironic.inspection.hooks` pipeline that + post-processes the inventory data the ramdisk reports before it's saved to + the node. This is where UnderStack matches a node to a flavor and resource + class — see [`redfish_inspect_understack.py`](#writing-a-new-hardware-interface) + below. +2. **Cleaning** — wiping disks and resetting BIOS/RAID/firmware settings + between tenants, or before first use. Implemented as **clean steps**: + methods decorated with `@ironic.drivers.base.clean_step(priority=...)` on + whichever interface owns that action (`management`, `deploy`, `raid`, ...). + Runs in the `cleaning` state. If the device has no deploy ramdisk to boot + (true of most network gear), every clean step it defines must pass + `requires_ramdisk=False` — see + [Interfaces for devices with no ramdisk or BMC agent](#interfaces-for-devices-with-no-ramdisk-or-bmc-agent). +3. **Deploying** — writing the tenant's image and configuration to the node. + Implemented as **deploy steps** + (`@ironic.drivers.base.deploy_step(priority=...)`), conventionally at + priority 100 for the main deploy method. Runs in the `deploying` state. +4. **Servicing** — post-deployment maintenance (e.g. a firmware update) on a + node that's already active, without a full teardown/redeploy. Implemented + as **service steps** (`@ironic.drivers.base.service_step(...)`), explicitly + requested by the caller rather than run automatically. Runs in the + `servicing` state. + +Clean, deploy, and service steps share the same shape: synchronous steps +return `None` and the conductor moves on; asynchronous steps return the +matching `WAIT` state and later call back (`continue_node_clean`, +`continue_node_deploy`, `continue_node_service`) to resume. `ironic-understack` +doesn't define any custom steps of these three kinds today — if you're adding +one, read the decorator docstrings in `ironic/drivers/base.py` and the +upstream [deploy steps guide](https://docs.openstack.org/ironic/latest/contributor/deploy-steps.html) +first. + +## How UnderStack plugs in + +`ironic-understack` ([`python/ironic-understack/`](https://github.com/rackerlabs/understack/tree/main/python/ironic-understack)) +is an ordinary Python package that Ironic loads via entry points, declared in +its `pyproject.toml`: + +| Entry point group | What it's for | UnderStack examples | +| --- | --- | --- | +| `ironic.hardware.types` | New hardware type | `netdev` | +| `ironic.hardware.interfaces.inspect` | New `inspect` interface implementation | `redfish-understack`, `idrac-redfish-understack` | +| `ironic.inspection.hooks` | Post-processing step for inspection data | `resource-class`, `update-baremetal-port`, `port-bios-name`, `node-name-check`, `chassis_model` | +| `ironic.api.middleware` | WSGI middleware on the Ironic API | `portgroup-name-validation` | +| `ironic.console.container` | Alternate console container backend | `kubernetes` | + +The package is baked into the Ironic container image at build time +(`COPY python/ironic-understack ...` in +[`containers/ironic/Dockerfile`](https://github.com/rackerlabs/understack/blob/main/containers/ironic/Dockerfile)), +so a new driver or interface needs both a code change here and a container +rebuild before it's live — check [`README.md`](https://github.com/rackerlabs/understack/blob/main/python/ironic-understack/README.md) +and [`DEVELOPMENT.md`](https://github.com/rackerlabs/understack/blob/main/python/ironic-understack/DEVELOPMENT.md) +for the local dev loop. + +For UnderStack's existing shipped hardware types, see the +[Ironic design reference](../design-guide/ironic.md). + +## Writing a new hardware interface + +Use this when an existing interface (Redfish, iDRAC, IPMI, ...) is *almost* +right but needs UnderStack-specific behavior layered on top. The pattern used +throughout `ironic_understack/drivers/` is a mixin over the upstream class, +registered under the same entry point group as the interface you're +extending. + +`ironic_understack/drivers/redfish_inspect_understack.py` extends the stock +Redfish and iDRAC-Redfish `inspect` interfaces to match discovered hardware +against a flavor: + +```python +class FlavorInspectMixin: + def inspect_hardware(self, task): + upstream_state = super().inspect_hardware(task) + # ... read inspection data, match a flavor, set task.node.resource_class + return upstream_state + + +class UnderstackRedfishInspect(FlavorInspectMixin, RedfishInspect): + ... + + +class UnderstackDracRedfishInspect(FlavorInspectMixin, DracRedfishInspect): + ... +``` + +The steps to follow this pattern for a different interface (say, a custom +`management` behavior): + +1. Pick the upstream class you're extending, e.g. + `ironic.drivers.modules.redfish.management.RedfishManagement`. +2. Subclass it (with a mixin if the same logic should apply to more than one + base class), calling `super().(task)` and only adding what's + different. +3. Register it under the matching entry point group in `pyproject.toml`, e.g.: + + ```toml + [project.entry-points."ironic.hardware.interfaces.management"] + my-management-understack = "ironic_understack.drivers.my_module:MyManagement" + ``` + +4. Add it to `enabled_management_interfaces` in `ironic.conf` and to the + relevant hardware type's `supported_management_interfaces` list (see + below) so Ironic actually picks it up. +5. Write a unit test alongside the existing ones in + `ironic_understack/tests/` — run with `pytest` (or `uv run pytest`) from + `python/ironic-understack/`. + +If you only need to react to inspection data rather than replace the whole +`inspect` interface, prefer an **inspection hook** +(`ironic.inspection.hooks`, see `ironic_understack/hooks/`) — it's a smaller +surface: implement `InspectionHook.__call__(self, task, inventory, +plugin_data)` and register it, no interface subclassing required. + +## Interfaces for devices with no ramdisk or BMC agent + +The pattern above — mix in over `RedfishInspect`/`RedfishManagement` — only +works when the device speaks Redfish (or IPMI, or has a sushy-compatible BMC). +A lot of the hardware UnderStack needs to manage doesn't: network gear like +switches and firewalls is typically managed out-of-band over SSH or a +vendor API, and never boots an Ironic deploy ramdisk at all. For that class of +device you write the interface from scratch against the abstract base +classes, not as a mixin over an existing implementation. + +`netdev_hardware.py`'s `NetdevHardware` (above) is **not** a template for +this — it wires every interface to a no-op precisely because it does no real +inspection or management. A device you actually want to inspect and clean +needs real implementations behind those two interfaces. + +**Inspect, from scratch.** Subclass `ironic.drivers.base.InspectInterface` +directly and implement the one abstract method, `inspect_hardware`. Nothing +requires a ramdisk here — everything is driven by however you talk to the +device (SSH, a vendor API, SNMP, ...): + +```python +from ironic.common import exception +from ironic.common import states +from ironic.drivers import base + + +class MyDeviceInspect(base.InspectInterface): + # Override if "memory_mb"/"local_gb"/"cpu_arch" don't apply to this + # device; leave empty rather than reporting properties that don't exist. + ESSENTIAL_PROPERTIES = set() + + def get_properties(self): + return {} + + def validate(self, task): + pass # check that credentials/connection info are present + + def inspect_hardware(self, task): + # Connect to the device, discover its properties/ports however is + # appropriate for it, then persist what you found: + # task.node.properties = {...} + # task.node.save() + # Create ironic Port objects for discovered NICs/interfaces as needed. + if : + raise exception.HardwareInspectionFailure(...) + return states.MANAGEABLE +``` + +**Cleaning, out-of-band.** Real cleaning behavior belongs on a real +`management` interface. You don't have to reimplement the boot-device +methods `ManagementInterface` requires (`get_supported_boot_devices`, +`set_boot_device`, `get_boot_device`) if they're meaningless for this +device — subclass `ironic.drivers.modules.noop_mgmt.NoopManagement` to inherit +sensible no-op defaults for those, then add your real clean step(s) on top. +The key detail is `requires_ramdisk=False`, since there's no ramdisk to wait +on: + +```python +from ironic.drivers import base +from ironic.drivers.modules import noop_mgmt + + +class MyDeviceManagement(noop_mgmt.NoopManagement): + def validate(self, task): + pass # check that credentials/connection info are present + + @base.clean_step(priority=10, requires_ramdisk=False) + def reset_to_factory_defaults(self, task): + # Talk to the device and reset its configuration. Return None when + # done (synchronous); return states.CLEANWAIT and later call + # continue_node_clean() if the reset is asynchronous. + ... +``` + +Wire both into a new hardware type (a sibling to `NetdevHardware`, not a +modification of it) via `supported_inspect_interfaces` and +`supported_management_interfaces`, register the entry points, and enable them +— same steps as [Writing a new hardware interface](#writing-a-new-hardware-interface) +and [Writing a new hardware type](#writing-a-new-hardware-type) above. Test +the clean step by calling it directly against a mocked connection/task, the +same way `test_netdev_hardware.py` tests interface selection without a +running Ironic service. + +## Writing a new hardware type + +Use this when a class of node needs a genuinely different combination of +interfaces, not just a tweak to one of them. +`ironic_understack/drivers/netdev_hardware.py` is UnderStack's example — a +hardware type for network devices that Ironic tracks only for Neutron port +binding, with every other interface set to a no-op: + +```python +class NetdevHardware(generic.ManualManagementHardware): + @property + def supported_deploy_interfaces(self): + return [noop.NoDeploy] + + @property + def supported_network_interfaces(self): + return [neutron.NeutronNetwork] + + # ... one supported__interfaces property per interface +``` + +To add your own: + +1. Subclass `ironic.drivers.hardware_type.AbstractHardwareType`, or more + usually `ironic.drivers.generic.GenericHardware` (or one of its variants + like `ManualManagementHardware`) to inherit sane defaults for interfaces + you don't need to change. +2. Override `supported__interfaces` for each interface where the + default isn't right, listing implementations in priority order (most + preferred first). +3. Register it: + + ```toml + [project.entry-points."ironic.hardware.types"] + my-hardware = "ironic_understack.drivers.my_hardware:MyHardware" + ``` + +4. Add it to `enabled_hardware_types` in `ironic.conf`, and make sure every + interface it lists is also present in the corresponding + `enabled__interfaces` option. +5. Test it the way `ironic_understack/tests/test_netdev_hardware.py` does — + instantiate the class directly and assert on + `supported__interfaces`, no running Ironic service required: + + ```python + def test_netdev_deploy(): + hw = NetdevHardware() + assert [c.__name__ for c in hw.supported_deploy_interfaces] == ["NoDeploy"] + ``` + +## Where to go next + +- [Ironic design reference](../design-guide/ironic.md) — what UnderStack + ships today and why. +- [Enabling Drivers](https://docs.openstack.org/ironic/latest/install/enabling-drivers.html) + — the operator side of `enabled_hardware_types` / `enabled__interfaces`. +- [Bare Metal State Machine](https://docs.openstack.org/ironic/latest/user/states.html) + — the full state diagram behind the "four operations" above. +- [`operator-guide/openstack-ironic-inspection-guide.md`](../operator-guide/openstack-ironic-inspection-guide.md) + — how inspection behaves operationally in UnderStack today. diff --git a/docs/design-guide/ironic.md b/docs/design-guide/ironic.md index 4e088806c..b7b31a730 100644 --- a/docs/design-guide/ironic.md +++ b/docs/design-guide/ironic.md @@ -1,5 +1,9 @@ # Ironic +See [Writing Ironic Drivers and Interfaces](../contributing/writing-ironic-drivers.md) +for how to add a new hardware type or interface. This page documents what +UnderStack ships today. + ## Custom Hardware Types UnderStack ships additional Ironic hardware types via the `ironic-understack` diff --git a/properdocs.yml b/properdocs.yml index a8c33936a..8d593b4b8 100644 --- a/properdocs.yml +++ b/properdocs.yml @@ -235,6 +235,7 @@ nav: - Contributing: - contributing/index.md - design-guide/add-remove-app.md + - contributing/writing-ironic-drivers.md - 'Design and Background': - openstack-helm.md - design-guide/ironic.md