Skip to content

Commit bbcf514

Browse files
authored
Merge pull request #25 from platform9/fix/live-validation
fix(lb,floatingip): live-validation fixes from the CE lab
2 parents 0366c97 + fcbd23a commit bbcf514

12 files changed

Lines changed: 167 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ All notable changes to this project are documented here. The format is based on
6161
changes per load balancer). PCD ships the **OVN** provider only, which is L4
6262
(TCP/UDP/SCTP); use L4 listener protocols and OVN-supported pool algorithms. L7
6363
policy/rule resources are omitted because the OVN provider does not support L7.
64+
`pcd_lb_loadbalancer` exposes `loadbalancer_provider` (defaults to `ovn`) — required
65+
because Octavia's server-side default provider is `amphora`, which PCD does not enable.
6466
- DNS (Designate v2) — Phase 3: `pcd_dns_zone` and `pcd_dns_recordset` resources plus a
6567
`pcd_dns_zone` data source. Zone and recordset create/update/delete are asynchronous,
6668
so applies wait for the object to reach `ACTIVE` (and to disappear after delete).

DECISIONS.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,38 @@ CheckDestroy). **PENDING** = code-complete and verified up to a lab-side limitat
1010
yet passable on this lab (reason noted). Generated registry docs are not committed; run
1111
`make generate` (tfplugindocs) to produce them.
1212

13+
### Live run on the fully-featured CE lab (2026-07-12)
14+
15+
Ran the acceptance suites against a CE lab with compute (KVM), image library, Cinder
16+
(Synology iSCSI), and DNS roles converged. **Green live:** identity, images, key
17+
management, the entire networking suite (network/subnet/secgroup/router/port/routes/QoS/
18+
quotas **and floating IPs**), compute control-plane (keypair/flavor/servergroup/quotaset),
19+
all three quotasets, and the **full load-balancer tree on OVN** (lb + TCP listener +
20+
SOURCE_IP_PORT pool + member + TCP monitor + data source).
21+
22+
**Provider bugs found and fixed by the live run:**
23+
- `pcd_lb_loadbalancer` had no `loadbalancer_provider` field, so Octavia fell back to its
24+
server-default `amphora` (which PCD does not enable) and **every LB create failed**.
25+
Added the field, defaulting to `ovn`.
26+
- `pcd_networking_floatingip` could not disassociate and produced "inconsistent result
27+
after apply" when the association changed: `port_id`/`fixed_ip` used
28+
`UseStateForUnknown` (which hid a removal) and the server-derived fields
29+
(`router_id`, `status`, `fixed_ip`) were not re-planned. Fixed with a `ModifyPlan`;
30+
inline disassociation is now `port_id = ""` (leaving it unset delegates to
31+
`pcd_networking_floatingip_associate`). Also fixed two test bugs surfaced here: the FIP
32+
tests lacked the router a floating IP needs, and `testAccCheckNetworkDestroy` matched
33+
data-source networks.
34+
35+
**Lab-side blockers (not provider defects — the resources create correctly and surface the
36+
real backend fault):**
37+
- Compute VM boot (`instance`, `interface_attach`, `volume_attach`): nova-compute cannot
38+
connect to Glance to fetch image **data** ("Failed to call glance method data") — an
39+
image-library data-path gap on the hypervisor.
40+
- Block storage `volume`: the `cinder-volume@synology` service is **down** (Synology NAS at
41+
192.168.1.25 unreachable or credentials wrong); volumes go straight to `error`.
42+
- DNS `zone`/`recordset`: Designate returns `500 no_servers_configured` — the pool has no
43+
nameservers (BIND9 / `pools.yaml` from the runbook's Phase E step 7 not applied).
44+
1345
| Family | Item | Status |
1446
|---|---|---|
1547
| Provider core | password auth + project scope, self-signed TLS (`insecure`), `pcd_identity_auth_scope` | **VALIDATED** |
@@ -21,12 +53,12 @@ yet passable on this lab (reason noted). Generated registry docs are not committ
2153
| Networking | `pcd_networking_network`, `_subnet`, `_secgroup`, `_secgroup_rule`, `_router`, `_router_interface` | **VALIDATED** |
2254
| Networking (DS) | `pcd_networking_network`, `_subnet`, `_secgroup` | **VALIDATED** |
2355
| Networking | `pcd_networking_port` | **PENDING** — code-complete, build/vet/lint/docs clean; acceptance test written (create/update/import). Not yet run live: lab credentials were unavailable in this session. No lab-side blocker expected. |
24-
| Networking | `pcd_networking_floatingip` | **PENDING** — code-complete. Needs an **external network** in the lab (allocation pool); acc test skips unless `PCD_ACC_EXTERNAL_NETWORK` names one. |
56+
| Networking | `pcd_networking_floatingip` | **VALIDATED** (2026-07-12) — allocate/associate/disassociate/import against an external network; the disassociation and inconsistent-result bugs found here are fixed. Requires an external network; the acc test builds the router path a floating IP needs and skips unless `PCD_ACC_EXTERNAL_NETWORK` names one. |
2557
| Networking (DS) | `pcd_networking_port`, `_port_ids`, `_router`, `_subnet_ids` | **PENDING** — code-complete; acc test written. Not yet run live (credentials unavailable this session). |
2658
| Networking (DS) | `pcd_networking_floatingip` | **PENDING** — depends on a floating IP existing (see external-network note above). |
2759
| Networking | `pcd_networking_router_route`, `_subnet_route` | **PENDING** — code-complete; single-route read-modify-write under a per-parent mutex so concurrent routes on one router/subnet don't clobber. Acc tests written (router route needs a router interface for a valid next-hop). Not yet run live. |
2860
| Networking | `pcd_networking_port_secgroup_associate` | **PENDING** — code-complete; shared (`enforce=false`) and exclusive (`enforce=true`) modes. Acc test written. Not yet run live. |
29-
| Networking | `pcd_networking_floatingip_associate` | **PENDING** — code-complete. Needs an external network (see note above); acc test skips unless `PCD_ACC_EXTERNAL_NETWORK` set. |
61+
| Networking | `pcd_networking_floatingip_associate` | **VALIDATED** (2026-07-12) — associates a separately-allocated floating IP to a port; acc test builds the router path and skips unless `PCD_ACC_EXTERNAL_NETWORK` set. |
3062
| Compute | `pcd_compute_keypair`, `_flavor`, `_servergroup` | **VALIDATED** |
3163
| Compute (DS) | `pcd_compute_flavor`, `_keypair`, `_availability_zones` | **VALIDATED** |
3264
| Compute | `pcd_compute_instance` (boot) | **PENDING** — lab image-library gap: images don't reach the onboarded host's local library → nova returns HTTP 204 for image data. Create/schedule/wait/error-report verified; passes with a library-backed image. |
@@ -36,7 +68,7 @@ yet passable on this lab (reason noted). Generated registry docs are not committ
3668
| Compute | `pcd_compute_volume_attach` | **PENDING** — code-complete; needs a booted instance **and** a Cinder backend (both lab-blocked). Best-effort volume waiter degrades gracefully without Cinder. |
3769
| Block storage | `pcd_blockstorage_volume` | **PENDING** — no Cinder storage backend on the lab (`storageBackends={}`); volumes go `creating → error`. Create + waiter + error-detection verified. |
3870
| Block storage (DS) | `pcd_blockstorage_volume`, `_snapshot` | **PENDING** — untestable without volumes on this lab. |
39-
| Load balancing (Octavia) | `pcd_lb_loadbalancer`, `_listener`, `_pool`, `_member`, `_monitor` + `_loadbalancer` DS | **PENDING** — Phase 3, code-complete; per-LB wait-for-`ACTIVE` lifecycle, root-LB resolution for every child, echo-only churny fields. Full-tree acc test + examples written. **PCD ships the OVN provider only** (verified on the CE lab: `providers=[ovn]`, no amphora), which is L4 — use TCP/UDP/SCTP listeners and OVN-supported pool algorithms; L7 policy/rule resources were removed (see the 2026-07-12 backout entry). LB provisioning needs a subnet (blocked by the lab's missing tenant-network pool), so not yet run live. |
71+
| Load balancing (Octavia) | `pcd_lb_loadbalancer`, `_listener`, `_pool`, `_member`, `_monitor` + `_loadbalancer` DS | **VALIDATED** (2026-07-12) — full-tree apply on OVN (lb + TCP listener + `SOURCE_IP_PORT` pool + member + TCP monitor + DS + rename + import). **PCD ships the OVN provider only** (`providers=[ovn]`, no amphora), which is L4 — use TCP/UDP/SCTP listeners and OVN pool algorithms; L7 policy/rule resources were removed (see backout entry). `loadbalancer_provider` was added (default `ovn`) because Octavia's server-side default `amphora` is not enabled — without it every create failed. |
4072
| DNS (Designate) | `pcd_dns_zone`, `pcd_dns_recordset` + `pcd_dns_zone` DS | **PENDING** — Phase 3, code-complete; async create/update/delete → wait-for-`ACTIVE`/404. Acc test (zone + recordset + import) + examples written. Designate is live on the lab (Step 0) and DNS needs no compute/storage backend, so this should pass live — not yet run this session (credentials unavailable). |
4173
| Key management (Barbican) | `pcd_keymanager_secret`, `pcd_keymanager_container` + `pcd_keymanager_secret` DS | **PENDING** — Phase 3, code-complete; write-only echo-only `payload`, URL-ref→UUID id handling, wait-for-`ACTIVE` only on create-with-payload. Acc test (secret + container + data source + import) + examples written. Barbican is live on the lab (Step 0) and needs no compute/storage backend, so this should pass live — not yet run this session (credentials unavailable). |
4274
| Network QoS (Neutron) | `pcd_networking_qos_policy`, `_qos_bandwidth_limit_rule`, `_qos_dscp_marking_rule`, `_qos_minimum_bandwidth_rule` + `_qos_policy` DS | **PENDING** — Phase 3, code-complete; rules nested under a policy with composite `<policy_id>/<rule_id>` import, tags via the attributes-tags extension (`qos/policies` type), `ForceNew` on `qos_policy_id`. Full-tree acc test (policy + all three rules + data source + import) + examples written. Depends only on the Neutron `qos` extension (no compute/storage backend), so this should pass live — not yet run this session (credentials unavailable). |

examples/resources/pcd_lb_listener/resource.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ resource "pcd_lb_loadbalancer" "example" {
1515
resource "pcd_lb_listener" "example" {
1616
name = "tf-example-listener"
1717
loadbalancer_id = pcd_lb_loadbalancer.example.id
18-
protocol = "HTTP"
18+
protocol = "TCP" # OVN provider is L4: TCP/UDP/SCTP
1919
protocol_port = 80
2020
}

examples/resources/pcd_lb_loadbalancer/resource.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ resource "pcd_networking_subnet" "example" {
88
}
99

1010
resource "pcd_lb_loadbalancer" "example" {
11-
name = "tf-example-lb"
12-
vip_subnet_id = pcd_networking_subnet.example.id
11+
name = "tf-example-lb"
12+
vip_subnet_id = pcd_networking_subnet.example.id
13+
loadbalancer_provider = "ovn" # PCD ships only the OVN (L4) provider; this is the default
1314
}

examples/resources/pcd_lb_monitor/resource.tf

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@ resource "pcd_lb_loadbalancer" "example" {
1515
resource "pcd_lb_pool" "example" {
1616
name = "tf-example-pool"
1717
loadbalancer_id = pcd_lb_loadbalancer.example.id
18-
protocol = "HTTP"
19-
lb_method = "ROUND_ROBIN"
18+
protocol = "TCP" # OVN provider is L4
19+
lb_method = "SOURCE_IP_PORT"
2020
}
2121

2222
resource "pcd_lb_monitor" "example" {
2323
pool_id = pcd_lb_pool.example.id
24-
type = "HTTP"
24+
type = "TCP"
2525
delay = 10
2626
timeout = 5
2727
max_retries = 3
28-
url_path = "/healthz"
2928
}

examples/resources/pcd_lb_pool/resource.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ resource "pcd_lb_loadbalancer" "example" {
1414

1515
resource "pcd_lb_listener" "example" {
1616
loadbalancer_id = pcd_lb_loadbalancer.example.id
17-
protocol = "HTTP"
17+
protocol = "TCP"
1818
protocol_port = 80
1919
}
2020

2121
resource "pcd_lb_pool" "example" {
2222
name = "tf-example-pool"
2323
listener_id = pcd_lb_listener.example.id
24-
protocol = "HTTP"
25-
lb_method = "ROUND_ROBIN"
24+
protocol = "TCP" # OVN provider is L4
25+
lb_method = "SOURCE_IP_PORT"
2626
}

internal/services/loadbalancer/loadbalancer_resource.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
2222
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
2323
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
24+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
2425
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2526
"github.com/hashicorp/terraform-plugin-framework/types"
2627

@@ -52,6 +53,7 @@ type loadBalancerModel struct {
5253
VipAddress types.String `tfsdk:"vip_address"`
5354
VipPortID types.String `tfsdk:"vip_port_id"`
5455
FlavorID types.String `tfsdk:"flavor_id"`
56+
Provider types.String `tfsdk:"loadbalancer_provider"`
5557
Tags types.Set `tfsdk:"tags"`
5658
ProvisioningStatus types.String `tfsdk:"provisioning_status"`
5759
OperatingStatus types.String `tfsdk:"operating_status"`
@@ -72,19 +74,20 @@ func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaReques
7274
"Layer 4 (TCP/UDP/SCTP): use L4 listener protocols and OVN-supported pool algorithms; HTTP/L7 features " +
7375
"are not available.",
7476
Attributes: map[string]schema.Attribute{
75-
"id": schema.StringAttribute{Computed: true, MarkdownDescription: "The load balancer ID.", PlanModifiers: useState},
76-
"name": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The name of the load balancer.", PlanModifiers: useState},
77-
"description": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "A description of the load balancer.", PlanModifiers: useState},
78-
"admin_state_up": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "The administrative state of the load balancer."},
79-
"vip_subnet_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Subnet on which to allocate the VIP (mutually exclusive with vip_network_id). Changing this forces a new resource.", PlanModifiers: forceNew},
80-
"vip_network_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Network on which to allocate the VIP (mutually exclusive with vip_subnet_id). Changing this forces a new resource.", PlanModifiers: forceNew},
81-
"vip_address": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The VIP address. Requesting a specific address changing it forces a new resource.", PlanModifiers: forceNew},
82-
"vip_port_id": schema.StringAttribute{Computed: true, MarkdownDescription: "The ID of the VIP port.", PlanModifiers: useState},
83-
"flavor_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The Octavia flavor to use. Changing this forces a new resource.", PlanModifiers: forceNew},
84-
"tags": schema.SetAttribute{Optional: true, Computed: true, ElementType: types.StringType, MarkdownDescription: "Tags applied to the load balancer.", PlanModifiers: []planmodifier.Set{setplanmodifier.UseStateForUnknown()}},
85-
"provisioning_status": schema.StringAttribute{Computed: true, MarkdownDescription: "The provisioning status (e.g. ACTIVE).", PlanModifiers: useState},
86-
"operating_status": schema.StringAttribute{Computed: true, MarkdownDescription: "The operating status (e.g. ONLINE).", PlanModifiers: useState},
87-
"region": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The region. Defaults to the provider's region.", PlanModifiers: useState},
77+
"id": schema.StringAttribute{Computed: true, MarkdownDescription: "The load balancer ID.", PlanModifiers: useState},
78+
"name": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The name of the load balancer.", PlanModifiers: useState},
79+
"description": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "A description of the load balancer.", PlanModifiers: useState},
80+
"admin_state_up": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "The administrative state of the load balancer."},
81+
"vip_subnet_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Subnet on which to allocate the VIP (mutually exclusive with vip_network_id). Changing this forces a new resource.", PlanModifiers: forceNew},
82+
"vip_network_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Network on which to allocate the VIP (mutually exclusive with vip_subnet_id). Changing this forces a new resource.", PlanModifiers: forceNew},
83+
"vip_address": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The VIP address. Requesting a specific address changing it forces a new resource.", PlanModifiers: forceNew},
84+
"vip_port_id": schema.StringAttribute{Computed: true, MarkdownDescription: "The ID of the VIP port.", PlanModifiers: useState},
85+
"flavor_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The Octavia flavor to use. Changing this forces a new resource.", PlanModifiers: forceNew},
86+
"loadbalancer_provider": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("ovn"), MarkdownDescription: "The Octavia provider driver. PCD ships only `ovn`, which is the default; changing this forces a new resource.", PlanModifiers: forceNew},
87+
"tags": schema.SetAttribute{Optional: true, Computed: true, ElementType: types.StringType, MarkdownDescription: "Tags applied to the load balancer.", PlanModifiers: []planmodifier.Set{setplanmodifier.UseStateForUnknown()}},
88+
"provisioning_status": schema.StringAttribute{Computed: true, MarkdownDescription: "The provisioning status (e.g. ACTIVE).", PlanModifiers: useState},
89+
"operating_status": schema.StringAttribute{Computed: true, MarkdownDescription: "The operating status (e.g. ONLINE).", PlanModifiers: useState},
90+
"region": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The region. Defaults to the provider's region.", PlanModifiers: useState},
8891
},
8992
}
9093
}
@@ -121,6 +124,7 @@ func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRe
121124
VipNetworkID: plan.VipNetworkID.ValueString(),
122125
VipAddress: plan.VipAddress.ValueString(),
123126
FlavorID: plan.FlavorID.ValueString(),
127+
Provider: plan.Provider.ValueString(),
124128
AdminStateUp: &adminUp,
125129
}
126130
if !plan.Tags.IsNull() && !plan.Tags.IsUnknown() {
@@ -286,6 +290,7 @@ func (r *loadBalancerResource) readInto(ctx context.Context, client *gophercloud
286290
m.VipAddress = types.StringValue(lb.VipAddress)
287291
m.VipPortID = types.StringValue(lb.VipPortID)
288292
m.FlavorID = types.StringValue(lb.FlavorID)
293+
m.Provider = types.StringValue(lb.Provider)
289294
m.ProvisioningStatus = types.StringValue(lb.ProvisioningStatus)
290295
m.OperatingStatus = types.StringValue(lb.OperatingStatus)
291296

0 commit comments

Comments
 (0)