Add DNSZone, DNSZoneShare, and RecordSet controllers for Designate support - #955
Closed
zunken1337 wants to merge 6 commits into
Closed
zunken1337 wants to merge 6 commits into
zunken1337 wants to merge 6 commits into
Conversation
…pport Closes the Designate gap tracked in k-orc#954: DNSZone/RecordSet CRUD, plus a new DNSZoneShare resource for Designate's zone-sharing API (grants another project co-management rights over a zone's recordsets without transferring ownership - confirmed live against a real OpenStack deployment via a create/list/delete round-trip before committing to this design over the alternative zone-transfer mechanism, which needs two different projects' credentials in one flow). All three Designate resources share one osclients.DNSClient (the same "one service, one client" convention NetworkClient already established for Neutron), rather than a dedicated client per kind. DNSZoneShare and RecordSet are both zone-scoped sub-resources in Designate's own API (Get/Create/Delete/Update all require the zone ID alongside the resource's own ID), which doesn't fit the generic actuator interfaces' single-ID GetOSResourceByID signature directly - resolved by having each 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. DNSZoneShare has no update operation at all in Designate's real API (confirmed via gophercloud - only List/Get/Share/Unshare) - both zoneRef and targetProjectID are immutable here, unlike RBACPolicy's analogous field. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
Corrects the 16 scaffolded KUTTL scenario directories (DNSZone, DNSZoneShare, RecordSet) from the scaffold tool's generic Name/Description assumptions to the real schema - DNSZone requires an explicit, period- terminated name (Designate rejects anything else, and there's no object- name fallback like most other resources have); DNSZoneShare/RecordSet dependencies all need a valid DNSZone with email set; DNSZoneShare's filter only has targetProjectID (no name/description), same import/import-error restructuring already done for RBACPolicy's analogous fields. Removed the dnszoneshare-update scenario entirely - DNSZoneShare has zero mutable fields, and K-ORC's own testing guide says the update test is only required "for resources that implement mutability". Also found and fixed a real bug while running the full suite one more time: dnszone_types.go's PRIMARY-zone-requires-email CEL rule had a curly/smart quote (U+201D) instead of a straight one in `self.email != ""`, breaking CEL compilation - confirmed this would have made the CRD uninstallable on any real Kubernetes API server, not just envtest, since controller-gen transcribes the rule string as-is without validating CEL syntax itself. Caught by the floatingip/image/router/etc. controller suites' own envtest setup (which installs every CRD, not just DNSZone's), not by DNSZone's own tests - a reminder that the full `go test ./...` run matters, not just the package under active work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
- dnszone_types.go: rewrite the PRIMARY-requires-email CEL rule to use has(self.email) instead of comparing against an empty string literal. Go's gofmt doc-comment formatter collapses an empty '' pair into a single curly quote, corrupting the CEL expression; avoiding the empty-string comparison (matching every other CEL rule in this repo, which already prefers has()) sidesteps it entirely. Add MinLength:=1 to Email so an explicit empty string still can't satisfy "has". - dnszone_types.go, recordset_types.go: add the kube-api-linter-required +kubebuilder:validation:items:MaxLength marker to the Masters/Records status array fields (same pattern as Subnet's DNSNameservers). - .golangci.yml: scope an exclusion for DNSZoneShare.TargetProjectID's noopenstackidref finding, same rationale and precedent as RBACPolicy.TargetProjectID (PR k-orc#953) - it's a raw OpenStack project ID by design, not a reference to an ORC Project object. - config/manifests/bases/orc.clusterserviceversion.yaml: add the missing DNSZone/DNSZoneShare/RecordSet CSV entries that `make generate-bundle` needs committed (was causing the generate workflow's git diff check to fail). - Regenerate CRD YAMLs and crd-reference.md to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
All 16 DNSZone/DNSZoneShare/RecordSet KUTTL scenarios failed in CI (all 3 devstack flavors) with "failed to create dns service client: No suitable endpoint could be found in the service catalog" - the e2e workflow's devstack deployment never enabled Designate, so there's no DNS service in the catalog for the new controllers to talk to. Add the same enable_plugin pattern already used for neutron/manila, with DESIGNATE_BACKEND_DRIVER=bind9 (devstack's own documented default/ lightest-weight backend, sufficient for zone/recordset CRUD against a real API - no external DNS infrastructure needed for these tests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
All 5 import/import-error/dependency-adjacent KUTTL scenarios that remained failing after enabling Designate in CI shared one root cause: newActuator() unconditionally resolved the owning zone via zoneDependency, which only reads spec.resource.zoneRef. An unmanaged (import) object never has spec.resource - only spec.import.filter - so the zone was never resolved, RequireDependency degenerated into "RequireDependencies returned empty depsMap, progressStatus, and error", and every import/import-error/dependency scenario for both kinds timed out waiting for a status that was never going to appear. Fix: newActuator now branches on which half of the object is populated, resolving the zone from spec.resource.zoneRef when managed and from the new spec.import.filter.zoneRef when unmanaged (same dual-dependency pattern ApplicationCredential already uses for userRef/userImportDependency). Both DNSZoneShareFilter and RecordSetFilter gained a required zoneRef field - Designate has no "list across all zones" endpoint for either shares or recordsets, so there was never a way to import one without already knowing its zone. Bare import.id (no filter) has no field to carry a zone reference at all; rather than leave it to hang, newActuator now returns a clear Terminal error for that case, called out in the PR description for maintainer awareness. While fixing this, found and fixed a real, separate bug the previous (never-triggered-by-tests) code masked: DNSZoneShare's ListOSResourcesForImport/ListOSResourcesForAdoption listed every share under a zone but never actually filtered by targetProjectID, despite a comment claiming "the generic reconciler's own filter-matching" would do it (it doesn't - every other actuator in this codebase does its own client-side filtering, same as applicationcredential's pattern this now follows). A zone with shares to two different target projects would previously have an even chance of importing/adopting the wrong one. dnszoneshare-import-error's KUTTL scenario now exercises this directly: two shares in the *same* zone, an ambiguous zoneRef-only filter, asserting the "more than one match" error. Verified via `make generate`, `make lint` (0 issues), and `make test` (full envtest/CEL suite + unit tests, 529/529 apivalidations specs, zero regressions) on admin3 before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
Collaborator
|
Thanks a lot for all the contributions the last couple of days, this is really welcome. Just know that it may take some time before we can give the proper attention to each one of them. I'll try my best to provide feedback soon. That said, this PR in particular would probably benefit from being split in 3 separate PRs that we'll merge in order (+16k LOC is way too big to review). |
Two unrelated, genuine test-fixture bugs found via this round's e2e failures (the code itself was fine - the controller correctly reported these as Designate 400s / a stable waiting state): - recordset-dependency and recordset-import used a RecordSet name that wasn't actually a subdomain of its own zoneRef's zone (e.g. "www.recordset-dependency-no-dnszone.example.com." under zone "recordset-dependency-pending.example.com." - a sibling domain, not a child of it). Designate correctly rejects this with 400 "invalid_recordset_location: RecordSet is not contained within its parent zone". recordset-create-minimal/-full/-update already got this right (name "www.<zone-name>"); import/dependency didn't. - dnszoneshare-import's 00-assert.yaml still expected the old "Waiting for OpenStack resource to be created externally" message, left over from before the previous commit moved the referenced zone's creation into step 01 (so the import object could spend a step genuinely waiting on its zoneRef dependency). At step 00 the zone doesn't exist yet, so the real - and stable - message is "Waiting for DNSZone/dnszoneshare-import to be created"; the stale assert text never matched, so the test never progressed past step 00 at all. (gazpacho's one-off recordset-create-full failure in the same run reached Available: True/Success by the time logs were collected - a timing flake under concurrent load, not a real issue.) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Joakim Johansson <joakimjohansson@cleura.nu>
This was referenced Oct 2, 2026
Contributor
Author
|
Thanks for the guidance - split into three stacked PRs as suggested:
Closing this one in favor of those three. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #954. Adds Designate DNS support:
DNSZone(zone CRUD, with theprojectID-in-status gapfrom #825's review fixed), a new
DNSZoneShareresource for Designate's zone-sharing API, andRecordSetfor individual DNS records within a zone.Design question for maintainers: Share vs Transfer
Designate has two separate mechanisms for giving another project access to a zone:
transfer_requests/transfer_accepts) - moves full ownership, needs twodifferent projects' credentials in one flow (owner creates the zone + transfer request,
recipient accepts). Same two-credential shape already raised in Enhancement: Glance image member sharing (grant + accept) #948 for Glance image sharing.
zones/{id}/shares) - grants another project co-management rights over azone's recordsets while the original project keeps ownership. Single credential only.
DNSZoneShareimplements Share, not Transfer, deliberately - it's dramatically simpler (onecredential, no accept-side resource needed, same shape as the
RBACPolicycontroller for Neutronnetwork sharing), and we think it's the more broadly useful mechanism to support first. We
confirmed Zone Share is a real, working API on our own OpenStack deployment (a live
create/list/delete round-trip, not just reading the gophercloud bindings) before committing to
this design. Transfer could be a follow-up matching the #948 pattern if there's demand for it.
Architecture notes
osclients.DNSClient(the "one service, one client" conventionNetworkClientalready established for Neutron), rather than a dedicated client per kind.DNSZoneShareandRecordSetare both zone-scoped sub-resources in Designate's own API(
Get/Create/Delete/Updateall require the zone ID alongside the resource's own ID),which doesn't fit the generic actuator interfaces' single-ID
GetOSResourceByIDsignaturedirectly. Resolved by having each 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.
DNSZoneSharehas no update operation at all in Designate's real API (confirmed viagophercloud - only
List/Get/Share/Unshare) - bothzoneRefandtargetProjectIDareimmutable here, unlike
RBACPolicy's analogoustargetProjectIDfield.DNSZone.spec.resource.name(andRecordSet's) must be explicit and end with a period -Designate rejects anything else, and unlike most other resources there's no sensible
object-name fallback (the ORC object's own Kubernetes name won't have a trailing period).
needs a zone reference even when unmanaged (no
spec.resourceto readzoneReffrom) - bothDNSZoneShareFilterandRecordSetFiltergained a requiredzoneReffield, resolved via thesame
Dependency/*ImportDependencysplitApplicationCredentialalready uses foruserRef/userImportDependency. One known gap flagged here rather than silently worked around:import.id(a bare OpenStack ID, no filter) isn't supported forDNSZoneShareorRecordSet- there's no field anywhere to carry a zone reference in that mode, and Designatehas no zone-independent way to look up either resource by ID alone.
newActuatorreturns aclear
Terminalerror for this case rather than hanging; happy to hear design ideas if thisgap matters to someone.
Testing
DNSZone: description/ttl/masters;RecordSet: description/ttl/records - both compared as sets, not positionally).test/apivalidationssuite extended with immutability, required-field, and PRIMARY/SECONDARY cross-field CEL validation tests for all three resources.
DNSZoneShare'supdatescenariodeliberately omitted - it has zero mutable fields, and the project's own testing guide says the
update test is only required "for resources that implement mutability").
deployment rather than assumed from documentation:
target_project_idisn't validated againstKeystone (any string accepted, confirmed via a real
openstack zone share createcall with agarbage value), and an exact-duplicate share on the same zone is rejected but differing by zone
is fine.
go test ./...run (not just the package under activework): a curly/smart quote had snuck into one CEL rule's string literal, which would have made
the CRD uninstallable on any real Kubernetes API server (
controller-gendoesn't validate CELsyntax, it just transcribes the rule string - the breakage only surfaced when envtest tried to
actually install the CRD).
never had it enabled - added
enable_plugin designate ...+DESIGNATE_BACKEND_DRIVER=bind9to
.github/workflows/e2e.yaml, same pattern already used for neutron/manila.Test plan
go build ./...,go vet ./...,gofmt -l .cleango test ./...passes (only unrelatedtest/e2e, which needs a live kind cluster, fails inthis environment)
make generateoutput committed (CRDs, deepcopy, adapters/controllers,applyconfiguration/clientset/lister/informer, mocks, API reference docs)
🤖 Generated with Claude Code