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

Fields

No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions