Skip to content

Commit e5d39ba

Browse files
authored
Merge pull request #43 from platform9/pushkar/host-role-v1-endpoint
resmgr: cluster roles, clusters, provider networks — full zero-to-hero support
2 parents d2cd8d1 + a0f76d3 commit e5d39ba

21 files changed

Lines changed: 1409 additions & 25 deletions

CHANGELOG.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,70 @@ All notable changes to this project are documented here. The format is based on
66

77
## [Unreleased]
88

9+
## [0.1.4] - 2026-08-14
10+
11+
### Fixed
12+
- **`pcd_host_role` could not assign a role at all.** Create and Delete issued
13+
`PUT`/`DELETE /resmgr/v2/hosts/<id>/roles/<name>`, but resmgr exposes no writable roles
14+
sub-resource on v2 and answers `404 RoleNotFound`; role assignment lives only on v1. Every
15+
`pcd_host_role` apply failed against PCD 2026.4. The resource now uses a v1 client.
16+
Blueprints and host configs are unaffected — they exist only on v2 (`/resmgr/v1/blueprint`
17+
and `/resmgr/v1/hostconfigs` both 404) and continue to use it.
18+
- **`pcd_host_role` reported permanent drift**, which would have persisted even once the
19+
write path was fixed. Read compared the configured role name against `GET /v2/hosts/<id>`,
20+
whose `roles` are mapped "uber-roles" (`hypervisor`) rather than the granular `pf9-*` names
21+
a role is assigned by, so the match never succeeded and Terraform removed the resource from
22+
state and recreated it on every plan. Read now uses v1, which reports granular names.
23+
- **`pcd_cluster_blueprint` showed a `storage_backends_json` diff on every plan.** resmgr
24+
echoes the blob with its own spacing and in insertion order, while `jsonencode()` emits
25+
compact output with sorted keys — semantically identical, textually different, so Terraform
26+
reported an in-place update that never converged. The read-back is now canonicalised
27+
(compact, keys sorted) in both the resource and the data source.
28+
29+
### Fixed (found by one-shot region bring-up validation)
30+
- **`pcd_host_cluster_role.wait_until_converged` could return early during onboarding.**
31+
`role_status` aggregates only the roles assigned at that moment, so while several cluster
32+
roles were being assigned concurrently there was a window where it read `ok` before the
33+
others landed — un-gating downstream resources (an image upload against a Glance that was
34+
not serving yet). The wait now also requires the cluster role's own granular marker
35+
(e.g. `pf9-glance-role` for `image-library`) to report applied.
36+
- **`pcd_cluster` creation failed on freshly deployed regions.** resmgr answers
37+
`500 Request Failed` to `POST /v2/clusters` until the compute control plane is warm
38+
(the PCD UI health-checks Nova before offering the dialog); the identical request
39+
succeeds minutes later. Create now retries 500s for a bounded window so a single apply
40+
can bring up a region from nothing.
41+
42+
### Added
43+
- **New resource `pcd_host_cluster_role`** — assigns PCD *cluster roles* (`hypervisor`,
44+
`image-library`, `persistent-storage`, `dns`) via the resmgr v2 uber-role API, the same
45+
call the PCD UI onboards hosts with. The control plane expands a cluster role into its
46+
granular `pf9-*` roles and computes their settings from the cluster blueprint and the
47+
host's host configuration (`persistent-storage` takes a `backends` list naming entries in
48+
the blueprint's `storage_backends_json`; `hypervisor` takes `host_cluster`, which PCD
49+
2026.4 requires). An optional `wait_until_converged` blocks until the host reports
50+
`role_status = ok` — tolerating the transient `failed` flaps normal onboarding produces —
51+
so a single configuration can onboard a hypervisor and boot instances on it in one apply.
52+
Assignment and removal retry through resmgr's transient `409 RoleUpdateConflict` while a
53+
host is converging. This closes the gap that made a fresh region impossible to bring up
54+
with Terraform alone: `pcd_host_role` applies granular roles with *default* settings,
55+
which wedges the host on settings-bearing roles (see its documentation for when it is
56+
still appropriate).
57+
- **New resource `pcd_cluster`** — manages PCD clusters (host clusters / host groups), the
58+
unit hypervisors join and the scope for VM high-availability, auto-rebalancing, GPU, and
59+
CPU-model settings. Required by `pcd_host_cluster_role`'s `hypervisor` role, whose
60+
`host_cluster` names it.
61+
- `Config.ResmgrV1Client()` alongside `ResmgrV2Client()`. An `endpoint_overrides` entry for
62+
`resmgr` now names the service rather than one of its API versions: the required version is
63+
applied to it, replacing any version the override already carries, so a single override
64+
serves both clients.
65+
- **`pcd_networking_network` gains `segments`** — provider-network attributes (admin only),
66+
mirroring `openstack_networking_network_v2`. A single segment creates a physical network
67+
(`network_type` `flat`/`vlan` on a `physical_network` label, optional `segmentation_id`),
68+
sent as top-level `provider:*` attributes; multiple segments use Neutron's multi-provider
69+
`segments` form. Create-only and not refreshed from the API, matching the upstream
70+
provider's behavior. Without this, provider networks — including any external network —
71+
could not be created by Terraform at all.
72+
973
## [0.1.3] - 2026-08-14
1074

1175
### Changed

docs/resources/cluster.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "pcd_cluster Resource - PCD"
4+
subcategory: "Cluster Blueprint"
5+
description: |-
6+
Manages a PCD cluster (host cluster / host group): the unit hypervisors join. Assigning a host the hypervisor cluster role requires one — pcd_host_cluster_role.host_cluster names it. Carries the cluster-scoped VM high-availability, auto-rebalancing, GPU, and CPU-model settings.
7+
---
8+
9+
# pcd_cluster (Resource)
10+
11+
Manages a PCD **cluster** (host cluster / host group): the unit hypervisors join. Assigning a host the `hypervisor` cluster role requires one — `pcd_host_cluster_role.host_cluster` names it. Carries the cluster-scoped VM high-availability, auto-rebalancing, GPU, and CPU-model settings.
12+
13+
## Example Usage
14+
15+
```terraform
16+
# A cluster (host cluster / host group) is the unit hypervisors join. VM HA
17+
# and auto-rebalancing are cluster-scoped settings.
18+
resource "pcd_cluster" "main" {
19+
name = "cluster-1"
20+
21+
vm_high_availability = {
22+
enabled = true
23+
}
24+
25+
auto_resource_rebalancing = {
26+
enabled = true
27+
rebalancing_strategy = "vm_workload_consolidation"
28+
rebalancing_frequency_mins = 20
29+
}
30+
}
31+
32+
# Hypervisors join the cluster through their cluster role.
33+
resource "pcd_host_cluster_role" "hypervisor" {
34+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
35+
role = "hypervisor"
36+
host_cluster = pcd_cluster.main.name
37+
}
38+
```
39+
40+
<!-- schema generated by tfplugindocs -->
41+
## Schema
42+
43+
### Required
44+
45+
- `name` (String) The cluster name. Changing this forces a new resource.
46+
47+
### Optional
48+
49+
- `auto_resource_rebalancing` (Attributes) Automatic workload-rebalancing settings. (see [below for nested schema](#nestedatt--auto_resource_rebalancing))
50+
- `cpu` (Attributes) Cluster CPU-model settings. Omit for PCD's default. (see [below for nested schema](#nestedatt--cpu))
51+
- `gpu` (Attributes) GPU passthrough/virtualization settings. (see [below for nested schema](#nestedatt--gpu))
52+
- `vm_high_availability` (Attributes) VM high-availability settings. (see [below for nested schema](#nestedatt--vm_high_availability))
53+
54+
<a id="nestedatt--auto_resource_rebalancing"></a>
55+
### Nested Schema for `auto_resource_rebalancing`
56+
57+
Optional:
58+
59+
- `enabled` (Boolean) Whether auto-rebalancing is enabled.
60+
- `rebalancing_frequency_mins` (Number) Rebalancing frequency in minutes.
61+
- `rebalancing_strategy` (String) `vm_workload_consolidation` or `node_resource_consolidation`.
62+
63+
64+
<a id="nestedatt--cpu"></a>
65+
### Nested Schema for `cpu`
66+
67+
Optional:
68+
69+
- `mode` (String) The CPU mode (e.g. `custom`); null means default.
70+
- `model` (String) The CPU model when `mode = "custom"`.
71+
72+
73+
<a id="nestedatt--gpu"></a>
74+
### Nested Schema for `gpu`
75+
76+
Optional:
77+
78+
- `enabled` (Boolean) Whether GPU support is enabled.
79+
- `mode` (String) The GPU mode.
80+
81+
82+
<a id="nestedatt--vm_high_availability"></a>
83+
### Nested Schema for `vm_high_availability`
84+
85+
Optional:
86+
87+
- `enabled` (Boolean) Auto-detect host failure and recover VMs.
88+
89+
## Import
90+
91+
Import is supported using the following syntax:
92+
93+
```shell
94+
terraform import pcd_cluster.main <cluster_name>
95+
```
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "pcd_host_cluster_role Resource - PCD"
4+
subcategory: "Cluster Blueprint"
5+
description: |-
6+
Assigns a PCD cluster role (resmgr v2 uber-role) to an onboarded host: hypervisor, image-library, persistent-storage, or dns. The PCD control plane expands the cluster role into its granular pf9-* roles and computes their settings from the cluster blueprint and the host's host configuration, so the host converges without hand-written role settings. This is the resource to onboard hosts with; pcd_host_role is the low-level granular API underneath it.
7+
---
8+
9+
# pcd_host_cluster_role (Resource)
10+
11+
Assigns a PCD **cluster role** (resmgr v2 uber-role) to an onboarded host: `hypervisor`, `image-library`, `persistent-storage`, or `dns`. The PCD control plane expands the cluster role into its granular `pf9-*` roles and computes their settings from the cluster blueprint and the host's host configuration, so the host converges without hand-written role settings. This is the resource to onboard hosts with; `pcd_host_role` is the low-level granular API underneath it.
12+
13+
## Example Usage
14+
15+
```terraform
16+
# Cluster roles are how hosts are onboarded: PCD expands each one into its
17+
# granular pf9-* roles, with settings computed from the cluster blueprint and
18+
# the host's host configuration.
19+
20+
# A compute host. host_cluster is required for the hypervisor role (create the
21+
# cluster with pcd_cluster). wait_until_converged blocks until the host reports
22+
# role_status = ok, so instances can be scheduled by resources later in the
23+
# same apply.
24+
resource "pcd_host_cluster_role" "hypervisor" {
25+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
26+
role = "hypervisor"
27+
host_cluster = pcd_cluster.main.name
28+
wait_until_converged = true
29+
}
30+
31+
# Image library (Glance) on the same host.
32+
resource "pcd_host_cluster_role" "image_library" {
33+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
34+
role = "image-library"
35+
}
36+
37+
# Block storage: `backends` names entries from the blueprint's
38+
# storage_backends_json (its top-level keys).
39+
resource "pcd_host_cluster_role" "storage" {
40+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
41+
role = "persistent-storage"
42+
backends = ["synology"]
43+
}
44+
```
45+
46+
<!-- schema generated by tfplugindocs -->
47+
## Schema
48+
49+
### Required
50+
51+
- `host_id` (String) The resmgr host UUID. Changing this forces a new resource.
52+
- `role` (String) The cluster role: `hypervisor`, `image-library`, `persistent-storage`, or `dns`. Changing this forces a new resource.
53+
54+
### Optional
55+
56+
- `backends` (List of String) For `persistent-storage` only: the storage backend names to enable on this host, as named in the cluster blueprint's `storage_backends_json` (its top-level keys).
57+
- `host_cluster` (String) For `hypervisor` only: the host cluster (host aggregate) to join.
58+
- `wait_until_converged` (Boolean) Wait until the host reports `role_status = ok` before completing. Role convergence installs and configures services on the host and typically takes several minutes. Enable this when later resources in the same configuration need the host operational (e.g. booting an instance on a freshly onboarded hypervisor).
59+
60+
### Read-Only
61+
62+
- `id` (String) The composite `<host_id>/<role>` ID.
63+
64+
## Import
65+
66+
Import is supported using the following syntax:
67+
68+
```shell
69+
terraform import pcd_host_cluster_role.hypervisor <host_id>/hypervisor
70+
```

docs/resources/host_role.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
page_title: "pcd_host_role Resource - PCD"
44
subcategory: "Cluster Blueprint"
55
description: |-
6-
Assigns a PCD role (e.g. pf9-ostackhost-neutron) to a host. Use one resource per host↔role pair. Role-specific settings are applied with their defaults.
6+
Assigns a granular PCD role (e.g. pf9-ostackhost-neutron) to a host via the low-level resmgr v1 API. Use one resource per host↔role pair. Role settings are applied with their defaults.
7+
~> Onboard hosts with pcd_host_cluster_role instead. Cluster roles (hypervisor, image-library, persistent-storage, dns) make the control plane compute each granular role's settings from the cluster blueprint and host configuration. This resource applies a role's default settings, and for roles whose settings are required — notably pf9-cindervolume-config, whose default backends is empty — that produces a convergence failure that blocks every role on the host and cannot be repaired while the host is converging (resmgr answers 409). Reserve this resource for roles that genuinely take no settings.
78
---
89

910
# pcd_host_role (Resource)
1011

11-
Assigns a PCD role (e.g. `pf9-ostackhost-neutron`) to a host. Use one resource per host↔role pair. Role-specific settings are applied with their defaults.
12+
Assigns a granular PCD role (e.g. `pf9-ostackhost-neutron`) to a host via the low-level resmgr v1 API. Use one resource per host↔role pair. Role settings are applied with their defaults.
13+
14+
~> **Onboard hosts with `pcd_host_cluster_role` instead.** Cluster roles (`hypervisor`, `image-library`, `persistent-storage`, `dns`) make the control plane compute each granular role's settings from the cluster blueprint and host configuration. This resource applies a role's *default* settings, and for roles whose settings are required — notably `pf9-cindervolume-config`, whose default `backends` is empty — that produces a convergence failure that blocks every role on the host and cannot be repaired while the host is converging (resmgr answers 409). Reserve this resource for roles that genuinely take no settings.
1215

1316
## Example Usage
1417

docs/resources/networking_network.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ resource "pcd_networking_network" "example" {
2121
external = false
2222
tags = ["tf-example", "networking"]
2323
}
24+
25+
# Provider network (admin): a flat physical network on a host-config label.
26+
resource "pcd_networking_network" "provider" {
27+
name = "lab-provider-net"
28+
shared = true
29+
30+
segments = [{
31+
network_type = "flat"
32+
physical_network = "physnet1"
33+
}]
34+
}
2435
```
2536

2637
<!-- schema generated by tfplugindocs -->
@@ -33,6 +44,7 @@ resource "pcd_networking_network" "example" {
3344
- `external` (Boolean) Whether the network has an external routing facility.
3445
- `name` (String) The name of the network.
3546
- `region` (String) The region. Defaults to the provider's region.
47+
- `segments` (Attributes List) Provider-network segments (admin only). One segment creates a physical network (e.g. `network_type = "flat"` / `"vlan"` on a `physical_network` label from the host config); multiple segments create a multi-provider network. Create-only: segments are not refreshed from the API and cannot be imported. Changing this forces a new resource. (see [below for nested schema](#nestedatt--segments))
3648
- `shared` (Boolean) Whether the network is shared across projects.
3749
- `tags` (Set of String) Tags applied to the network.
3850
- `tenant_id` (String) The owning project. Changing this forces a new resource.
@@ -41,6 +53,15 @@ resource "pcd_networking_network" "example" {
4153

4254
- `id` (String) The network ID.
4355

56+
<a id="nestedatt--segments"></a>
57+
### Nested Schema for `segments`
58+
59+
Optional:
60+
61+
- `network_type` (String) The segment type: `flat`, `vlan`, `vxlan`, or `geneve`.
62+
- `physical_network` (String) The physical network label (e.g. `physnet1`, as mapped in the host configuration's `network_labels`).
63+
- `segmentation_id` (Number) The segmentation ID (e.g. VLAN ID). Omit for `flat`.
64+
4465
## Import
4566

4667
Import is supported using the following syntax:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terraform import pcd_cluster.main <cluster_name>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# A cluster (host cluster / host group) is the unit hypervisors join. VM HA
2+
# and auto-rebalancing are cluster-scoped settings.
3+
resource "pcd_cluster" "main" {
4+
name = "cluster-1"
5+
6+
vm_high_availability = {
7+
enabled = true
8+
}
9+
10+
auto_resource_rebalancing = {
11+
enabled = true
12+
rebalancing_strategy = "vm_workload_consolidation"
13+
rebalancing_frequency_mins = 20
14+
}
15+
}
16+
17+
# Hypervisors join the cluster through their cluster role.
18+
resource "pcd_host_cluster_role" "hypervisor" {
19+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
20+
role = "hypervisor"
21+
host_cluster = pcd_cluster.main.name
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terraform import pcd_host_cluster_role.hypervisor <host_id>/hypervisor
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Cluster roles are how hosts are onboarded: PCD expands each one into its
2+
# granular pf9-* roles, with settings computed from the cluster blueprint and
3+
# the host's host configuration.
4+
5+
# A compute host. host_cluster is required for the hypervisor role (create the
6+
# cluster with pcd_cluster). wait_until_converged blocks until the host reports
7+
# role_status = ok, so instances can be scheduled by resources later in the
8+
# same apply.
9+
resource "pcd_host_cluster_role" "hypervisor" {
10+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
11+
role = "hypervisor"
12+
host_cluster = pcd_cluster.main.name
13+
wait_until_converged = true
14+
}
15+
16+
# Image library (Glance) on the same host.
17+
resource "pcd_host_cluster_role" "image_library" {
18+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
19+
role = "image-library"
20+
}
21+
22+
# Block storage: `backends` names entries from the blueprint's
23+
# storage_backends_json (its top-level keys).
24+
resource "pcd_host_cluster_role" "storage" {
25+
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
26+
role = "persistent-storage"
27+
backends = ["synology"]
28+
}

examples/resources/pcd_networking_network/resource.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,14 @@ resource "pcd_networking_network" "example" {
66
external = false
77
tags = ["tf-example", "networking"]
88
}
9+
10+
# Provider network (admin): a flat physical network on a host-config label.
11+
resource "pcd_networking_network" "provider" {
12+
name = "lab-provider-net"
13+
shared = true
14+
15+
segments = [{
16+
network_type = "flat"
17+
physical_network = "physnet1"
18+
}]
19+
}

0 commit comments

Comments
 (0)