Skip to content

Add integration test: deploy Keystone on kind (forge) + create a project via the admin account #293

Description

@berendt

Summary

Add a first integration test for openstack-project-manager that exercises
the real project-creation path against a live Keystone, not mocks. Keystone
is deployed locally on a kind cluster via
c5c3/forge (its ControlPlane Quick Start), and
the test then creates one project using the admin account and verifies it
through the OpenStack SDK.

This is deliberately a scaffold: one happy-path test. The goal is to stand
up the harness (kind + Keystone + clouds.yaml + a runnable assertion + CI
wiring) so that further integration tests can be added incrementally later.

Today the suite is unit-only (test/unit/, stdlib unittest, all OpenStack
clients mocked), run via the openstack-project-manager-tox Zuul job. Nothing
ever talks to a real Keystone, so regressions in the actual
openstack.connect() / keystone.projects.update() apply path can slip
through.

Reuse, don't reinvent

The kind/kubectl/Helm installation and the CI harness shape are copied from
the proven netbox-manager E2E work
— do not author a novel installer:

What we reuse from that PR (almost verbatim, adapted for this repo):

  1. Ansible pre-run playbook that installs pinned, checksum-verified
    kind/kubectl (+ Helm) and ensures Docker is running, using the
    ensure-pip / ensure-pipenv / ensure-docker roles.
  2. The IPv6 accept_ra=2 sysctl fix in the pre-run playbook. This is the
    non-obvious, hard-won bit: the OSISM Zuul nodes are IPv6-only and learn
    their default route via SLAAC/RA. When kind makes Docker create a
    dual-stack network it enables net.ipv6.conf.all.forwarding, after which the
    kernel (default accept_ra=1) stops honouring RAs and the node drops off the
    network mid-run (RESULT_UNREACHABLE). Setting accept_ra=2 keeps RAs
    honoured while forwarding is on. Carry this over.
  3. trap-based teardown in run.sh that always deletes the kind cluster on
    exit (success or failure), but only tears down a cluster this run actually
    created — a pre-existing debug cluster of the same name is reused and left in
    place. Plus a cluster diagnostics dump on failure before teardown.
  4. Makefile targets integration / integration-up / integration-down
    (analogous to netbox's e2e / e2e-up / e2e-down).
  5. Zuul job placed in periodic-daily (heavy, ~15 min) so the PR
    check gate stays fast — mirroring netbox's pre-e2e.yml / test-e2e.yml
    split and timeout: 2400.

Keystone via forge (only Keystone is needed)

Follow the forge ControlPlane Quick Start
(https://c5c3.github.io/forge/quick-start-controlplane.html) — only the
Keystone service is deployed:

# 1. toolchain (forge's own pinned installer → ~/.local/bin)
git clone https://github.com/c5c3/forge.git   # pin a ref!
cd forge
make install-test-deps           # pins kind v0.32.0, kubectl v1.36.1, jq, ...
export PATH="${HOME}/.local/bin:${PATH}"

# 2. infra + ControlPlane operator stack on a kind cluster (cluster name: "forge")
KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true make deploy-infra

Then apply a Keystone-only ControlPlane CR (namespace openstack) and
wait for it:

apiVersion: c5c3.io/v1alpha1
kind: ControlPlane
metadata:
  name: controlplane
  namespace: openstack
spec:
  openStackRelease: "2025.2"
  services:
    keystone:
      replicas: 1
      publicEndpoint: https://keystone.127-0-0-1.nip.io:8443/v3
      gateway:
        parentRef:
          name: openstack-gw
        hostname: keystone.127-0-0-1.nip.io
        path: /
kubectl apply -f controlplane.yaml
kubectl wait controlplane/controlplane -n openstack --for=condition=Ready --timeout=15m

forge also supports WITH_CONTROLPLANE_CR=true to auto-apply a bundled CR,
but that bundle may include more than Keystone. Since only Keystone is
needed
, we apply our own minimal CR (above), exactly as the Quick Start does.

Keystone access (no port-forward — the shared Envoy Gateway exposes it):

  • OS_AUTH_URL = https://keystone.127-0-0-1.nip.io:8443/v3 (nip.io → 127.0.0.1)
  • OS_USERNAME = admin, OS_PROJECT_NAME = admin
  • OS_USER_DOMAIN_NAME = Default, OS_PROJECT_DOMAIN_NAME = Default
  • Admin password from the operator-projected Secret:
    kubectl get secret controlplane-keystone-admin-credentials \
      -n openstack -o jsonpath='{.data.password}' | base64 -d
  • The endpoint uses a self-signed cert (cert-manager) → verify: false
    (a.k.a. --insecure).

How the test reaches the project-manager (clouds.yaml)

openstack-project-manager authenticates exclusively via clouds.yaml named
clouds; the create/manage commands default to --cloud admin. The harness
generates a clouds.yaml from the forge admin credentials:

clouds:
  admin:
    auth:
      auth_url: https://keystone.127-0-0-1.nip.io:8443/v3
      username: admin
      password: "<from controlplane-keystone-admin-credentials>"
      project_name: admin
      user_domain_name: Default
      project_domain_name: Default
    identity_api_version: 3
    verify: false     # self-signed cert behind the Envoy Gateway

Point the run at it via OS_CLIENT_CONFIG_FILE (or place it on the standard
search path).

The single test

Action — create one project as admin, using the real CLI path:

tox -e create -- --cloud admin --domain default --name opm-integration-test \
                 --quota-class basic --nocreate-user

(tox -e create runs python openstack_project_manager/create.py, see
tox.ini. --nocreate-user keeps the scaffold minimal — no per-user
application credential.)

Verify — assert via the OpenStack SDK in a standalone
test/integration/verify.py (mirroring netbox's verify.py), e.g.:

import openstack
cloud = openstack.connect(cloud="admin")
project = cloud.identity.find_project("opm-integration-test", domain_id=<default-domain-id>)
assert project is not None
# optional spot-checks: the quota-class property and the per-project group exist

The script exits non-zero on any failed assertion. run.sh then optionally
deletes the project to keep reruns idempotent.

⚠️ Must not be collected by the fast unit gate. tox -e test runs
python -m unittest discover ./test, which recurses. The verifier must be a
standalone script (not named test_*.py) — exactly the rationale
netbox-manager used for keeping verify.py out of pytest tests/unit.

Proposed deliverables (file-by-file)

Mirroring netbox-manager#263, adapted to this repo's layout (test/ singular,
no Helm needed, Keystone via forge instead of a chart):

File Purpose
test/integration/deploy_keystone.sh Clone forge @ pinned ref, make install-test-deps, KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true make deploy-infra, apply the Keystone-only ControlPlane CR, wait Ready, emit clouds.yaml. Safe to run standalone (make integration-up).
test/integration/run.sh Orchestrate: deploy → generate clouds.yaml → tox -e create -- …verify.py. trap-based teardown of a self-created cluster on every exit path + diagnostics dump on failure.
test/integration/verify.py Standalone openstacksdk assertions (project exists, etc.). Not a unittest/pytest test.
test/integration/controlplane.yaml The Keystone-only ControlPlane CR.
test/integration/README.md Prerequisites (Docker, forge), make usage, env overrides, what is asserted.
Makefile (new) integration (full cycle + teardown), integration-up (deploy + leave running), integration-down (kind delete cluster --name forge / forge make teardown-infra).
playbooks/pre-integration.yml Reuse netbox#263 pre-e2e.yml: ensure-pip/ensure-pipenv/ensure-docker roles, the IPv6 accept_ra=2 sysctl fix, ensure Docker running. (Tool binaries via forge's make install-test-deps, invoked from deploy_keystone.sh.)
playbooks/test-integration.yml Install deps into the venv, run make integration.
.zuul.yaml New job openstack-project-manager-integration (nodeset: ubuntu-noble, pre-run: playbooks/pre-integration.yml, run: playbooks/test-integration.yml, timeout: 2400); add to periodic-daily only (keep check fast).

Open decisions (please confirm during implementation)

  1. Toolchain install source. Recommended: reuse netbox#263's node-prep
    (Docker + IPv6 fix + pip) and let forge's own make install-test-deps
    install the pinned kind/kubectl/jq (authoritative for forge, avoids
    version drift; forge pins kind v0.32.0, the same version netbox#263 pins).
    Alternative: port netbox#263's pinned get_url download tasks and align
    versions with forge. Either way we are not writing a novel installer.
  2. forge pinning. Pin a specific forge git ref/tag in deploy_keystone.sh
    for reproducibility (forge is a fast-moving prototyping repo).
  3. Project-manager invocation flags. Confirm the minimal create flags work
    against a bare forge Keystone — create.py also creates a <domain>-admin
    group, a per-project group, assigns default roles, and sets quota-class
    properties on the project. Verify the default Keystone ships the expected
    roles (admin/member/reader) and Default domain; adjust flags
    (--nocreate-user, --quota-class basic) if any step is unsupported.
  4. CI placement. Recommend periodic-daily only (heavy job). If a PR-gate
    signal is wanted, it can additionally be added to check.
  5. Resources. forge's kind-config.yaml targets a single node ~7 GB RAM /
    2 vCPU; confirm the ubuntu-noble nodeset matches what netbox#263 uses.

Acceptance criteria

  • make integration on a Docker-capable host: provisions kind + Keystone
    via forge, creates one project as admin, asserts it via the SDK, and tears
    the cluster down on every exit path (incl. failure).
  • make integration-up / make integration-down work for local debugging.
  • New openstack-project-manager-integration Zuul job runs in
    periodic-daily; the check gate is unchanged and stays fast.
  • The verifier is not collected by tox -e test.
  • test/integration/README.md documents prerequisites and usage.
  • No real credentials are committed; the generated clouds.yaml is ephemeral.

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Ready for review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions