Sitelet https://github.com/k-orc/openstack-resource-controller/pull/957
Skip to content

Add RecordSet controller for Designate DNS record support - #957

Open
zunken1337 wants to merge 2 commits into
k-orc:mainfrom
zunken1337:new-controller-recordset
Open

zunken1337 wants to merge 2 commits into
k-orc:mainfrom
zunken1337:new-controller-recordset

Conversation

@zunken1337

Copy link
Copy Markdown
Contributor

Summary

Second of three Designate controllers split out of #955 at @mandre's request
(#955 (comment)). Adds
RecordSet (individual DNS records within a zone). Stacked on #956 (DNSZone) - every
RecordSet operation is scoped under its owning zone, and this PR extends the shared
osclients.DNSClient #956 introduces. Please merge #956 first; this diff will shrink to just
the RecordSet-specific changes once that happens.

Architecture notes

  • Extends osclients.DNSClient (from Add DNSZone controller for Designate DNS zone support #956) with List/Create/Delete/Get/UpdateRecordSet.
  • RecordSet is a zone-scoped sub-resource in Designate's own API (Get/Create/Delete/
    Update all require the zone ID alongside the recordset's own ID), which doesn't fit the
    generic actuator interfaces' single-ID GetOSResourceByID signature directly. Resolved by
    having the actuator resolve its owning zone once at construction time and reuse that across
    every method call on the same instance, rather than re-resolving per call.
  • RecordSet.spec.resource.name must be explicit and end with a period, same reasoning as
    DNSZone.spec.resource.name in Add DNSZone controller for Designate DNS zone support #956.
  • Importing a RecordSet (unmanaged + import.filter) needs a zoneRef in the filter itself,
    since Designate's recordset list API is zone-scoped and there's no "list across all zones"
    endpoint - RecordSetFilter gained a required zoneRef field, resolved via the same dual
    Dependency/*ImportDependency pattern ApplicationCredential already uses for
    userRef/userImportDependency.

Testing

  • Unit test for the one mutable-field update path (description/ttl/records, records compared as
    a set since Designate doesn't guarantee return order).
  • Full test/apivalidations suite extended with immutability and required-field CEL validation
    tests.
  • All 6 required KUTTL e2e scenarios, including recordset-dependency and recordset-import,
    which specifically need the recordset's name to be an actual subdomain of its zone (Designate
    rejects invalid_recordset_location otherwise - a real mistake caught and fixed during this
    work, not just a hypothetical).

Test plan

  • go build ./..., go vet ./..., gofmt -l . clean
  • go test ./... passes (only unrelated test/e2e, which needs a live kind cluster, fails in
    this environment)
  • make generate output committed
  • make lint clean (0 issues)
  • KUTTL e2e suite run against a real OpenStack cloud in CI

🤖 Generated with Claude Code

Joakim Johansson and others added 2 commits October 2, 2026 07:45
First of three Designate controllers split out of a single oversized
PR (zone, then DNSZoneShare and RecordSet as separate follow-up PRs)
at mandre's request - ~16k LOC in one PR was too large to review.

DNSZone covers zone CRUD (PRIMARY/SECONDARY, AXFR masters, serial,
transferredAt) against a new shared osclients.DNSClient, following
the same "one OpenStack service, one client" convention NetworkClient
already established for Neutron. DNSZoneShare and RecordSet will each
add their own methods to this same client in their own PRs, since all
three are Designate resources on one ServiceClient.

Credit: this picks up the gap left by @eshulman2's stalled k-orc#825 (zone
CRUD only, no project-ownership transfer, no RecordSet) with a fresh
implementation built via the current scaffolding, rather than
rebasing k-orc#825 through months of generic-controller-framework changes
- the path mandre suggested. k-orc#825 gets closed/superseded by this
series; eshulman2's original design exploration there is gratefully
acknowledged and is what first surfaced several of the real gaps this
PR fixes (a projectID-in-status field that was missing entirely, and
Masters typed as unvalidated []string rather than a real IP type).

Full test coverage: unit test for the one mutable-field update path
(description/ttl/masters, masters compared as a set not positionally),
the full test/apivalidations envtest/CEL suite, and all 5 required
KUTTL e2e scenarios. Verified via `make generate`, `make lint` (0
issues), and `make test` (full suite, zero regressions) on real
hardware before pushing - gofmt specifically checked byte-for-byte
after a prior session found it can corrupt an empty-string CEL
comparison into a smart quote; avoided here by using has() instead,
which also matches every other CEL rule already in this codebase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
Second of three Designate controllers split out of a single
oversized PR at mandre's request (DNSZone, then this, then
DNSZoneShare) - stacked on new-controller-dnszone since every
RecordSet operation is scoped under its owning DNSZone.

Extends the shared osclients.DNSClient (added in the DNSZone PR) with
List/Create/Delete/Get/UpdateRecordSet. RecordSet's zoneRef is
resolved once per actuator construction and reused across every
method call, since Designate's recordset API requires the zone ID
alongside the recordset's own ID for every operation (including Get) -
a single-ID GetOSResourceByID can't carry both, so the zone is
resolved once at actuator-construction time instead of per call.

Full test coverage: unit test for the one mutable-field update path
(description/ttl/records, records compared as a set since Designate
doesn't guarantee return order), the full test/apivalidations
envtest/CEL suite, and all 6 required KUTTL e2e scenarios - including
recordset-dependency and recordset-import, which need the recordset's
name to be an actual subdomain of its zone (Designate rejects
"invalid_recordset_location" otherwise) and a zoneRef in the import
filter (Designate's recordset list API is zone-scoped, so importing
needs a zone reference even when unmanaged - same dual zoneRef/
zoneImportRef dependency pattern ApplicationCredential already uses
for userRef).

Verified via `make generate`, `make lint` (0 issues), and `make test`
(full suite, zero regressions) on real hardware before pushing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
@zunken1337

Copy link
Copy Markdown
Contributor Author

The `gazpacho` e2e failure is a known timing flake in `recordset-create-full`, not a real issue - I've now seen this exact same single test fail on this exact flavor three separate times across this work (twice in the predecessor combined PR #955, now here), always for the same reason: the resource genuinely reconciles successfully, just not within KUTTL's 240s step timeout under concurrent CI load.

From this run's collected artifacts (`orc-managed-resources/openstack.k-orc.cloud_recordsets.yaml`), the object was created at `06:27:08Z` and reached `Available: True` / `Success` at `06:27:19Z` - 11 seconds later, well inside a normal reconcile window. The other two devstack flavors (epoxy, flamingo) and every other check pass clean on this PR. No code change made for this - didn't want to paper over a real timing issue with a longer timeout without more data, but flagging it here in case it's useful signal (e.g. if `gazpacho`, the newest OpenStack version flavor, runs under tighter resource constraints in CI than the other two).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver:major Breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant