Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,70 @@ All notable changes to this project are documented here. The format is based on

## [Unreleased]

## [0.1.4] - 2026-08-14

### Fixed
- **`pcd_host_role` could not assign a role at all.** Create and Delete issued
`PUT`/`DELETE /resmgr/v2/hosts/<id>/roles/<name>`, but resmgr exposes no writable roles
sub-resource on v2 and answers `404 RoleNotFound`; role assignment lives only on v1. Every
`pcd_host_role` apply failed against PCD 2026.4. The resource now uses a v1 client.
Blueprints and host configs are unaffected — they exist only on v2 (`/resmgr/v1/blueprint`
and `/resmgr/v1/hostconfigs` both 404) and continue to use it.
- **`pcd_host_role` reported permanent drift**, which would have persisted even once the
write path was fixed. Read compared the configured role name against `GET /v2/hosts/<id>`,
whose `roles` are mapped "uber-roles" (`hypervisor`) rather than the granular `pf9-*` names
a role is assigned by, so the match never succeeded and Terraform removed the resource from
state and recreated it on every plan. Read now uses v1, which reports granular names.
- **`pcd_cluster_blueprint` showed a `storage_backends_json` diff on every plan.** resmgr
echoes the blob with its own spacing and in insertion order, while `jsonencode()` emits
compact output with sorted keys — semantically identical, textually different, so Terraform
reported an in-place update that never converged. The read-back is now canonicalised
(compact, keys sorted) in both the resource and the data source.

### Fixed (found by one-shot region bring-up validation)
- **`pcd_host_cluster_role.wait_until_converged` could return early during onboarding.**
`role_status` aggregates only the roles assigned at that moment, so while several cluster
roles were being assigned concurrently there was a window where it read `ok` before the
others landed — un-gating downstream resources (an image upload against a Glance that was
not serving yet). The wait now also requires the cluster role's own granular marker
(e.g. `pf9-glance-role` for `image-library`) to report applied.
- **`pcd_cluster` creation failed on freshly deployed regions.** resmgr answers
`500 Request Failed` to `POST /v2/clusters` until the compute control plane is warm
(the PCD UI health-checks Nova before offering the dialog); the identical request
succeeds minutes later. Create now retries 500s for a bounded window so a single apply
can bring up a region from nothing.

### Added
- **New resource `pcd_host_cluster_role`** — assigns PCD *cluster roles* (`hypervisor`,
`image-library`, `persistent-storage`, `dns`) via the resmgr v2 uber-role API, the same
call the PCD UI onboards hosts with. The control plane expands a cluster role into its
granular `pf9-*` roles and computes their settings from the cluster blueprint and the
host's host configuration (`persistent-storage` takes a `backends` list naming entries in
the blueprint's `storage_backends_json`; `hypervisor` takes `host_cluster`, which PCD
2026.4 requires). An optional `wait_until_converged` blocks until the host reports
`role_status = ok` — tolerating the transient `failed` flaps normal onboarding produces —
so a single configuration can onboard a hypervisor and boot instances on it in one apply.
Assignment and removal retry through resmgr's transient `409 RoleUpdateConflict` while a
host is converging. This closes the gap that made a fresh region impossible to bring up
with Terraform alone: `pcd_host_role` applies granular roles with *default* settings,
which wedges the host on settings-bearing roles (see its documentation for when it is
still appropriate).
- **New resource `pcd_cluster`** — manages PCD clusters (host clusters / host groups), the
unit hypervisors join and the scope for VM high-availability, auto-rebalancing, GPU, and
CPU-model settings. Required by `pcd_host_cluster_role`'s `hypervisor` role, whose
`host_cluster` names it.
- `Config.ResmgrV1Client()` alongside `ResmgrV2Client()`. An `endpoint_overrides` entry for
`resmgr` now names the service rather than one of its API versions: the required version is
applied to it, replacing any version the override already carries, so a single override
serves both clients.
- **`pcd_networking_network` gains `segments`** — provider-network attributes (admin only),
mirroring `openstack_networking_network_v2`. A single segment creates a physical network
(`network_type` `flat`/`vlan` on a `physical_network` label, optional `segmentation_id`),
sent as top-level `provider:*` attributes; multiple segments use Neutron's multi-provider
`segments` form. Create-only and not refreshed from the API, matching the upstream
provider's behavior. Without this, provider networks — including any external network —
could not be created by Terraform at all.

## [0.1.3] - 2026-08-14

### Changed
Expand Down
95 changes: 95 additions & 0 deletions docs/resources/cluster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "pcd_cluster Resource - PCD"
subcategory: "Cluster Blueprint"
description: |-
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.
---

# pcd_cluster (Resource)

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.

## Example Usage

```terraform
# A cluster (host cluster / host group) is the unit hypervisors join. VM HA
# and auto-rebalancing are cluster-scoped settings.
resource "pcd_cluster" "main" {
name = "cluster-1"

vm_high_availability = {
enabled = true
}

auto_resource_rebalancing = {
enabled = true
rebalancing_strategy = "vm_workload_consolidation"
rebalancing_frequency_mins = 20
}
}

# Hypervisors join the cluster through their cluster role.
resource "pcd_host_cluster_role" "hypervisor" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "hypervisor"
host_cluster = pcd_cluster.main.name
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) The cluster name. Changing this forces a new resource.

### Optional

- `auto_resource_rebalancing` (Attributes) Automatic workload-rebalancing settings. (see [below for nested schema](#nestedatt--auto_resource_rebalancing))
- `cpu` (Attributes) Cluster CPU-model settings. Omit for PCD's default. (see [below for nested schema](#nestedatt--cpu))
- `gpu` (Attributes) GPU passthrough/virtualization settings. (see [below for nested schema](#nestedatt--gpu))
- `vm_high_availability` (Attributes) VM high-availability settings. (see [below for nested schema](#nestedatt--vm_high_availability))

<a id="nestedatt--auto_resource_rebalancing"></a>
### Nested Schema for `auto_resource_rebalancing`

Optional:

- `enabled` (Boolean) Whether auto-rebalancing is enabled.
- `rebalancing_frequency_mins` (Number) Rebalancing frequency in minutes.
- `rebalancing_strategy` (String) `vm_workload_consolidation` or `node_resource_consolidation`.


<a id="nestedatt--cpu"></a>
### Nested Schema for `cpu`

Optional:

- `mode` (String) The CPU mode (e.g. `custom`); null means default.
- `model` (String) The CPU model when `mode = "custom"`.


<a id="nestedatt--gpu"></a>
### Nested Schema for `gpu`

Optional:

- `enabled` (Boolean) Whether GPU support is enabled.
- `mode` (String) The GPU mode.


<a id="nestedatt--vm_high_availability"></a>
### Nested Schema for `vm_high_availability`

Optional:

- `enabled` (Boolean) Auto-detect host failure and recover VMs.

## Import

Import is supported using the following syntax:

```shell
terraform import pcd_cluster.main <cluster_name>
```
70 changes: 70 additions & 0 deletions docs/resources/host_cluster_role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "pcd_host_cluster_role Resource - PCD"
subcategory: "Cluster Blueprint"
description: |-
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.
---

# pcd_host_cluster_role (Resource)

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.

## Example Usage

```terraform
# Cluster roles are how hosts are onboarded: PCD expands each one into its
# granular pf9-* roles, with settings computed from the cluster blueprint and
# the host's host configuration.

# A compute host. host_cluster is required for the hypervisor role (create the
# cluster with pcd_cluster). wait_until_converged blocks until the host reports
# role_status = ok, so instances can be scheduled by resources later in the
# same apply.
resource "pcd_host_cluster_role" "hypervisor" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "hypervisor"
host_cluster = pcd_cluster.main.name
wait_until_converged = true
}

# Image library (Glance) on the same host.
resource "pcd_host_cluster_role" "image_library" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "image-library"
}

# Block storage: `backends` names entries from the blueprint's
# storage_backends_json (its top-level keys).
resource "pcd_host_cluster_role" "storage" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "persistent-storage"
backends = ["synology"]
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `host_id` (String) The resmgr host UUID. Changing this forces a new resource.
- `role` (String) The cluster role: `hypervisor`, `image-library`, `persistent-storage`, or `dns`. Changing this forces a new resource.

### Optional

- `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).
- `host_cluster` (String) For `hypervisor` only: the host cluster (host aggregate) to join.
- `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).

### Read-Only

- `id` (String) The composite `<host_id>/<role>` ID.

## Import

Import is supported using the following syntax:

```shell
terraform import pcd_host_cluster_role.hypervisor <host_id>/hypervisor
```
7 changes: 5 additions & 2 deletions docs/resources/host_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
page_title: "pcd_host_role Resource - PCD"
subcategory: "Cluster Blueprint"
description: |-
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.
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.
~> 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.
---

# pcd_host_role (Resource)

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.
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.

~> **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.

## Example Usage

Expand Down
21 changes: 21 additions & 0 deletions docs/resources/networking_network.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ resource "pcd_networking_network" "example" {
external = false
tags = ["tf-example", "networking"]
}

# Provider network (admin): a flat physical network on a host-config label.
resource "pcd_networking_network" "provider" {
name = "lab-provider-net"
shared = true

segments = [{
network_type = "flat"
physical_network = "physnet1"
}]
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -33,6 +44,7 @@ resource "pcd_networking_network" "example" {
- `external` (Boolean) Whether the network has an external routing facility.
- `name` (String) The name of the network.
- `region` (String) The region. Defaults to the provider's region.
- `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))
- `shared` (Boolean) Whether the network is shared across projects.
- `tags` (Set of String) Tags applied to the network.
- `tenant_id` (String) The owning project. Changing this forces a new resource.
Expand All @@ -41,6 +53,15 @@ resource "pcd_networking_network" "example" {

- `id` (String) The network ID.

<a id="nestedatt--segments"></a>
### Nested Schema for `segments`

Optional:

- `network_type` (String) The segment type: `flat`, `vlan`, `vxlan`, or `geneve`.
- `physical_network` (String) The physical network label (e.g. `physnet1`, as mapped in the host configuration's `network_labels`).
- `segmentation_id` (Number) The segmentation ID (e.g. VLAN ID). Omit for `flat`.

## Import

Import is supported using the following syntax:
Expand Down
1 change: 1 addition & 0 deletions examples/resources/pcd_cluster/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import pcd_cluster.main <cluster_name>
22 changes: 22 additions & 0 deletions examples/resources/pcd_cluster/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# A cluster (host cluster / host group) is the unit hypervisors join. VM HA
# and auto-rebalancing are cluster-scoped settings.
resource "pcd_cluster" "main" {
name = "cluster-1"

vm_high_availability = {
enabled = true
}

auto_resource_rebalancing = {
enabled = true
rebalancing_strategy = "vm_workload_consolidation"
rebalancing_frequency_mins = 20
}
}

# Hypervisors join the cluster through their cluster role.
resource "pcd_host_cluster_role" "hypervisor" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "hypervisor"
host_cluster = pcd_cluster.main.name
}
1 change: 1 addition & 0 deletions examples/resources/pcd_host_cluster_role/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import pcd_host_cluster_role.hypervisor <host_id>/hypervisor
28 changes: 28 additions & 0 deletions examples/resources/pcd_host_cluster_role/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Cluster roles are how hosts are onboarded: PCD expands each one into its
# granular pf9-* roles, with settings computed from the cluster blueprint and
# the host's host configuration.

# A compute host. host_cluster is required for the hypervisor role (create the
# cluster with pcd_cluster). wait_until_converged blocks until the host reports
# role_status = ok, so instances can be scheduled by resources later in the
# same apply.
resource "pcd_host_cluster_role" "hypervisor" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "hypervisor"
host_cluster = pcd_cluster.main.name
wait_until_converged = true
}

# Image library (Glance) on the same host.
resource "pcd_host_cluster_role" "image_library" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "image-library"
}

# Block storage: `backends` names entries from the blueprint's
# storage_backends_json (its top-level keys).
resource "pcd_host_cluster_role" "storage" {
host_id = "04575315-80ce-4617-9b96-6611d00c9942"
role = "persistent-storage"
backends = ["synology"]
}
11 changes: 11 additions & 0 deletions examples/resources/pcd_networking_network/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ resource "pcd_networking_network" "example" {
external = false
tags = ["tf-example", "networking"]
}

# Provider network (admin): a flat physical network on a host-config label.
resource "pcd_networking_network" "provider" {
name = "lab-provider-net"
shared = true

segments = [{
network_type = "flat"
physical_network = "physnet1"
}]
}
Loading