Skip to content

SeanMooney/ard

Repository files navigation

ARD

ARD is a local development and test harness for VM-backed DevStack deployments. The primary local workflow uses Ansible provider playbooks to provision libvirt VMs, generate an ARD/Zuul-like inventory, and then run the existing DevStack deployment roles inside those VMs.

The current local provider target is libvirt. KubeVirt is planned as a provider, OpenShift-related workloads are supported, and static is now available for deployments on pre-provisioned SSH hosts.

Docs navigation was reorganized; see:

  • docs/repo-navigation.md for repo layout and entry points
  • docs/README.md for the documentation index
  • ansible/README.md for Ansible structure and canonical playbook paths

Quick start

Bootstrap the repository and host dependencies:

./bootstrap-repo.sh

Render a named deployment workspace:

make render ARD_DEPLOYMENT=devstack-a

Create the VMs and generated inventory:

make apply ARD_DEPLOYMENT=devstack-a
make ping ARD_DEPLOYMENT=devstack-a

SSH to a deployment node, or print the SSH command without running it:

make ssh ARD_DEPLOYMENT=devstack-a ARD_NODE=controller
make ssh-print ARD_DEPLOYMENT=devstack-a ARD_NODE=compute-1
# equivalent explicit dry-run/print mode
make ssh ARD_DEPLOYMENT=devstack-a ARD_NODE=compute-1 ARD_SSH_PRINT=1

Deploy DevStack:

make deploy ARD_DEPLOYMENT=devstack-a
make verify ARD_DEPLOYMENT=devstack-a

Destroy provider resources when finished:

make destroy ARD_DEPLOYMENT=devstack-a

Remove the local deployment workspace only when you no longer need the rendered inputs or generated state:

make cleanup ARD_DEPLOYMENT=devstack-a

Local deployment workflow

The normal local workflow is:

  1. render: create a concrete deployment workspace from presets and optional overlays.
  2. apply: create libvirt network/domain/disk/seed resources, generate inventory, and wait for SSH/cloud-init readiness.
  3. ping: verify SSH/Ansible connectivity again.
  4. deploy: run the multinode DevStack playbook.
  5. verify: run basic post-deploy checks.
  6. destroy: remove libvirt resources but keep the workspace and generated artifacts for inspection.
  7. clean-generated: remove generated inventory/state/rendered artifacts without touching provider resources.
  8. cleanup: delete the workspace.

A deployment workspace lives under:

deployments/<deployment-name>/
  deployment.yaml
  nodes.yaml
  devstack/
    common.yaml
    group_vars/
      controller.yaml
      compute.yaml
    host_vars/
  inventory.yaml          # generated by apply
  provider-state.yaml     # generated by apply
  rendered/               # generated provider artifacts
  logs/

render treats deployment.yaml, nodes.yaml, and devstack/*.yaml as generated output and may overwrite them. Keep custom intent in a render file or deployment-local overlay such as overrides/render.yaml.

Make targets

The root Makefile wraps the provider playbooks. The default target is a full local rebuild workflow:

make
# equivalent to
make default

make default runs destroy-clean-generated, cleanup, render, apply, ping, deploy, and verify in order. The initial destroy and cleanup steps are best-effort so the target also works from a fresh checkout.

Individual workflow targets are also available:

make render
make apply
make ping
make ssh
make ssh-print
make deploy
make verify
make destroy
make destroy-clean-generated
make clean-generated
make cleanup
make site

Useful variables:

ARD_DEPLOYMENT       deployment name, default devstack-1
ARD_DEPLOYMENTS_DIR  deployment parent dir, default ./deployments
ARD_DEPLOYMENT_DIR   full workspace path
ARD_PROVIDER         provider, currently libvirt
ARD_TOPOLOGY         topology preset
ARD_TARGET_BRANCH    DevStack target branch, default master
ARD_WORKLOAD         workload to deploy, default devstack; use microshift for MicroShift
ARD_SERVICES         comma-separated service profiles, default devstack,ovn,tempest; empty for MicroShift
ARD_PROVIDER_PROFILE provider profile, default local-libvirt
ARD_IMAGE            optional image key override; defaults to centos-stream-10 for MicroShift
ARD_NETWORK_CIDR     libvirt management CIDR, default 192.168.96.0/24
ARD_RENDER_FILE      optional render intent file loaded before Make vars
ARD_NODE             inventory node for make ssh, default controller
ARD_SSH_PRINT        print SSH command without running it when set to 1
ARD_SSH_ARGS         extra arguments passed to ssh
ARD_EXTRA_VARS       extra Ansible vars appended to provider commands

Example:

make render \
  ARD_DEPLOYMENT=devstack-a \
  ARD_TARGET_BRANCH=master \
  ARD_TOPOLOGY=one-controller-two-compute \
  ARD_SERVICES=devstack,ovn,tempest \
  ARD_NETWORK_CIDR=192.168.99.0/24

Multiple local deployments

Use a unique deployment name and management CIDR for each local deployment:

make render ARD_DEPLOYMENT=devstack-a ARD_NETWORK_CIDR=192.168.99.0/24
make render ARD_DEPLOYMENT=devstack-b ARD_NETWORK_CIDR=192.168.100.0/24

Provider resources are named with the deployment name, for example:

ard-devstack-a-controller
ard-devstack-a-compute-1

Inventory hostnames remain logical names such as controller, compute-1, and compute-2.

Topology presets

Supported local render presets:

all-in-one
one-controller-one-compute
one-controller-two-compute

all-in-one renders:

controller

one-controller-one-compute renders:

controller
compute-1

one-controller-two-compute renders:

controller
compute-1
compute-2

Topology presets are built from generic node pools. Singleton pools can set an explicit name such as controller; counted pools default to readable hyphenated names such as {type}-{index}. Multinode topologies disable nova-compute on the controller through the rendered controller group vars when the topology says the controller does not run compute services.

Render intent and service profiles

Render can start from a small intent file:

---
ard_provider: libvirt
ard_provider_profile: local-libvirt
ard_target_branch: stable/2026.1
ard_topology: one-controller-one-compute
ard_service_profiles:
  - devstack
  - ovn
  - tempest
ard_libvirt_network_cidr: 192.168.98.0/24

Use it with:

make render ARD_DEPLOYMENT=stable-test ARD_RENDER_FILE=examples/render.yaml

Use ard_render_overrides for simple kustomize-like customizations. Overrides use ordinary recursive dictionary merge semantics: later dictionaries replace scalar and list values for the relevant section.

ard_management_network: ard-mgmt
ard_render_overrides:
  provider_defaults:
    image: ubuntu-24.04
  node_pools:
    compute:
      count: 2
      flavor: devstack-compute
      profiles:
        - ssh
        - nested_virt
        - performance
      networks:
        - name: ard-mgmt
          ip_start: 3
        - name: storage
          ip_start: 20
  networks:
    storage:
      cidr: 192.168.120.0/24
      provider_network: ard-storage
  devstack:
    common:
      enable_ceph: true
    controller:
      controller_localrc_extra:
        DEBUG_LIBVIRT_COREDUMPS: true

ard_render_node_overrides:
  compute-2:
    image: ubuntu-24.04
    flavor: devstack-compute
    profiles:
      - ssh
      - nested_virt
      - gpu
    networks:
      ard-mgmt:
        ip: 192.168.98.50

The libvirt provider supports multiple rendered networks. ard_management_network selects which attached network is used for SSH inventory and nodepool.private_ipv4; additional networks are rendered as extra libvirt networks and attached as additional VM interfaces.

The built-in tenant network preset is isolated and opt-in. It is not attached by default, but can be added to a node pool when guests need a bridge-only network for their own VLANs, DHCP, or overlay experiments:

ard_render_overrides:
  node_pools:
    compute:
      networks:
        - name: ard-mgmt
          ip_start: 3
        - name: tenant
          mac_start: 20

Isolated networks render as libvirt networks without host-side IP, NAT, or DHCP. Guest interfaces are still given deterministic MAC addresses and stable interface names.

Current service profiles are:

devstack
ovn
tempest
ceph

Images and flavors

Reusable provider defaults live in the ARD provider common role. Deployment workspaces normally only select the image/flavor by name instead of embedding the full registry.

Current image keys:

debian-13
ubuntu-24.04
centos-stream-10
fedora-eln
almalinux-10
rocky-linux-10

Current flavor keys:

devstack-control   8 vCPU, 16 GiB RAM, 80 GiB disk
devstack-compute   8 vCPU,  8 GiB RAM, 80 GiB disk
microshift-node    8 vCPU,  8 GiB RAM, 80 GiB disk

The default local image is Debian 13 genericcloud.

Cache and state paths

Base cloud images are cached under:

$XDG_CACHE_HOME/ard/images

or, if XDG_CACHE_HOME is unset:

~/.cache/ard/images

Per-deployment libvirt disks, seed ISOs, NVRAM, and console logs live under:

$XDG_STATE_HOME/ard/libvirt/images/<deployment-name>

or, if XDG_STATE_HOME is unset:

~/.local/state/ard/libvirt/images/<deployment-name>

make destroy removes per-deployment provider resources but keeps cached base images and generated workspace artifacts for inspection. Use make destroy-clean-generated to destroy provider resources and then remove inventory.yaml, provider-state.yaml, and rendered/. Use make clean-generated to remove those generated files without touching provider resources.

Molecule scenarios

Top-level Molecule scenarios are full ARD/libvirt-backed DevStack validation flows. They call the same provider playbooks used by Make and do not use Vagrant. The scenario source of truth lives in molecule.yml under provisioner.ard; Molecule platforms are intentionally omitted so topology and node names are defined only once by ARD render presets.

Available scenarios:

default                    Debian 13, controller + compute-1, master
one-controller-two-compute Debian 13, controller + compute-1 + compute-2
stable-2026.1              Ubuntu 24.04, controller + compute-1, stable/2026.1

Run a full scenario test:

uv run molecule test -s default

For a cheaper loop:

uv run molecule create -s default
uv run ansible -i molecule/default/deployment/inventory.yaml all -m ping
uv run molecule converge -s default
uv run molecule verify -s default
uv run molecule destroy -s default

Role-level Molecule scenarios live under ansible/roles/*/molecule and use Podman where containers are sufficient:

make molecule-test
make molecule-role-ensure_kustomize

Troubleshooting

Libvirt access

The local provider uses qemu:///system. Your user normally needs libvirt/qemu group access. If bootstrap reports missing group membership, log out and back in or use newgrp libvirt before running provider commands.

UEFI firmware

Libvirt firmware auto-selection is used for UEFI boot with secure boot disabled. Install the OVMF/edk2 firmware package for your distribution if libvirt cannot define or start the domain.

SSH not ready

apply creates VMs and inventory, then waits for SSH and cloud-init completion before returning. If apply times out, inspect the serial console logs under the libvirt deployment state directory or retry:

make apply ARD_DEPLOYMENT=devstack-a

Molecule create uses the same apply playbook, so it also waits for node readiness before converging.

CIDR conflicts

Use a management CIDR that does not conflict with existing host networks or other ARD deployments:

make render ARD_DEPLOYMENT=devstack-b ARD_NETWORK_CIDR=192.168.100.0/24

Cleanup after failure

Destroy provider resources while preserving generated artifacts for inspection:

make destroy ARD_DEPLOYMENT=devstack-a

Destroy provider resources and remove generated inventory/state/rendered files:

make destroy-clean-generated ARD_DEPLOYMENT=devstack-a

Remove generated files without touching provider resources:

make clean-generated ARD_DEPLOYMENT=devstack-a

If the workspace is no longer needed:

make cleanup ARD_DEPLOYMENT=devstack-a

Generated runtime files are ignored by git:

inventory.yaml
provider-state.yaml
rendered/
logs/

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors