feat(lb): Octavia load-balancer family (pcd_lb_*) — Phase 3 - #16
Merged
Conversation
Phase 3, flagship family. New internal/services/loadbalancer package plus a LoadBalancerV2Client (catalog type "load-balancer") on the shared config. Resources: pcd_lb_loadbalancer (root, cascade delete + wait-for-DELETED), pcd_lb_listener, pcd_lb_pool (session persistence, clear via empty struct), pcd_lb_member, pcd_lb_monitor (HTTP probe fields only for HTTP/HTTPS), pcd_lb_l7policy, and pcd_lb_l7rule (composite import). Data source: pcd_lb_loadbalancer. Octavia serializes changes per load balancer: every child resource resolves its ROOT load balancer (listener -> its LB; pool -> LB or via listener; member/monitor -> pool -> LB; l7policy -> listener -> LB; l7rule -> policy -> listener -> LB) and waits for provisioning_status to return to ACTIVE before and after each mutation, so concurrent child edits don't hit 409 "immutable". Churny/server-filled collection fields (listener SNI/allowed_cidrs/insert_headers) are echo-only. Full-tree acceptance test, per-resource examples (with the "Load Balancer" registry subcategory), CHANGELOG, and DECISIONS matrix included. build/vet/gofmt/golangci-lint (0 issues) and tfplugindocs all clean.
…aches state pcd_lb_pool's loadbalancer_id and listener_id are both Optional+Computed. The user sets one; the other is planned unknown on create. readInto only filled them when BOTH were empty, so the sibling stayed unknown and every pool apply failed with 'inconsistent result after apply'. Fill each independently from the pool result (defaulting to an empty string), gated on IsNull/IsUnknown so a plain read never re-derives a sibling that would force replacement. Found by adversarial review.
PF9-pushkar
added a commit
that referenced
this pull request
Jul 14, 2026
feat(lb): Octavia load-balancer family (pcd_lb_*) — Phase 3
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.
First Phase 3 family — Octavia load balancing. New
internal/services/loadbalancerpackage + aLoadBalancerV2Client(catalog typeload-balancer) on the shared config.Resources & data source
pcd_lb_loadbalancerpcd_lb_listenerpcd_lb_poolpcd_lb_memberpcd_lb_monitorpcd_lb_l7policy/pcd_lb_l7rulepcd_lb_loadbalancer(data source)The defining complexity: Octavia serializes per load balancer
After any change to the LB or a child, the root LB enters
PENDING_*and the API rejects further changes with 409 until it returns toACTIVE. So every child resolves its root LB (listener→LB, pool→LB or via listener, member/monitor→pool→LB, l7policy→listener→LB, l7rule→policy→listener→LB) and waits forprovisioning_status=ACTIVEbefore and after each mutation. The root LB delete cascades and waits for 404.Churny/server-filled collection fields (listener
sni_container_refs/allowed_cidrs/insert_headers) are echo-only to avoid perpetual diffs.Adversarial review fix included
The review caught a real bug:
pcd_lb_pool'sloadbalancer_id/listener_idare mutually-exclusive Optional+Computed — the unset one is planned unknown on create, and the old code only filled them when both were empty, so an unknown reached state → "inconsistent result after apply" on every pool create. Fixed to fill each independently (gated onIsNull/IsUnknownso a plain read can't re-derive a sibling and force a spurious replace).Checks
go build/vet/gofmt/golangci-lint(0 issues),tfplugindocs generate(pages under the "Load Balancer" subcategory), andterraform fmton the new examples all clean. A full-tree acceptance test is included.Live-validation status
Code-complete; static checks green + adversarially reviewed. Not run against the CE lab — Octavia is live (Step 0) but LB provisioning needs a working amphora/provider driver, and lab credentials weren't available this session. Tracked in
DECISIONS.md.