Skip to content

Commit 19a99da

Browse files
authored
Merge pull request #1351 from rackerlabs/flavor-match-update
feat: flavor syncing via nova deployment hook and cleanups for consistency
2 parents bd4b598 + 3b54544 commit 19a99da

24 files changed

+602
-33
lines changed
File renamed without changes.

ansible/playbooks/keystone_bootstrap.yaml renamed to ansible/keystone_bootstrap.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
pre_tasks:
2121
- name: Check OpenStack connectivity
22-
ansible.builtin.import_tasks: ../tasks/check_openstack_auth.yml
22+
ansible.builtin.import_tasks: tasks/check_openstack_auth.yml
2323

2424
roles:
2525
- role: keystone_bootstrap
File renamed without changes.
File renamed without changes.

ansible/playbooks/openstack_network.yaml renamed to ansible/openstack_network.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
pre_tasks:
2121
- name: Check OpenStack connectivity
22-
ansible.builtin.import_tasks: ../tasks/check_openstack_auth.yml
22+
ansible.builtin.import_tasks: tasks/check_openstack_auth.yml
2323

2424
roles:
2525
- role: openstack_network
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
# Copyright (c) 2025 Rackspace Technology, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
- name: OpenStack Nova Bootstrap
17+
hosts: neutron
18+
connection: local
19+
20+
pre_tasks:
21+
- name: Check OpenStack connectivity
22+
ansible.builtin.import_tasks: tasks/check_openstack_auth.yml
23+
24+
roles:
25+
- role: nova_flavors

ansible/playbooks/openstack_octavia.yaml renamed to ansible/openstack_octavia.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
pre_tasks:
2121
- name: Check OpenStack connectivity
22-
ansible.builtin.import_tasks: ../tasks/check_openstack_auth.yml
22+
ansible.builtin.import_tasks: tasks/check_openstack_auth.yml
2323

2424
roles:
2525
- role: openstack_octavia
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Nova Flavors Ansible Role
2+
3+
This role creates and manages OpenStack Nova flavors based on UnderStack flavor and device-type definitions stored in ConfigMaps.
4+
5+
## Purpose
6+
7+
The `nova_flavors` role automates the creation of Nova flavors for bare metal provisioning by:
8+
9+
1. Reading flavor definitions from the `flavors` ConfigMap
10+
2. Reading device-type definitions from the `device-types` ConfigMap
11+
3. Matching flavor resource classes to device-type resource class specifications
12+
4. Creating Nova flavors with properties derived from device-types
13+
5. Configuring scheduling constraints using resource classes and traits
14+
15+
See the [UnderStack Flavors Design Guide](https://rackerlabs.github.io/understack/design-guide/flavors/) for architectural details.
16+
17+
## How It Works
18+
19+
### Example
20+
21+
**Flavor Definition** (`m1.small.nicX.yaml`):
22+
23+
```yaml
24+
name: m1.small.nicX
25+
resource_class: m1.small
26+
description: Small compute flavor with NICX network hardware - 16 cores, 128GB RAM, dual 480GB drives
27+
traits:
28+
- trait: NICX
29+
state: required
30+
```
31+
32+
**Device-Type Definition** (`dell-poweredge-r7615.yaml`):
33+
34+
```yaml
35+
class: server
36+
manufacturer: Dell
37+
model: PowerEdge R7615
38+
resource_class:
39+
- name: m1.small
40+
cpu:
41+
cores: 16
42+
model: AMD EPYC 9124
43+
memory:
44+
size: 131072 # MB (128 GB)
45+
drives:
46+
- size: 480 # GB
47+
- size: 480
48+
nic_count: 2
49+
```
50+
51+
**Resulting Nova Flavor**:
52+
53+
- **Name**: `m1.small.nicX`
54+
- **vCPUs**: 16 (from device-type cpu.cores)
55+
- **RAM**: 131072 MB (from device-type memory.size)
56+
- **Disk**: 480 GB (from device-type drives[0].size)
57+
- **Description**: "Small compute flavor with NICX network hardware - 16 cores, 128GB RAM, dual 480GB drives" (from flavor.description)
58+
- **Extra Specs**:
59+
- `resources:VCPU='0'` - bare metal doesn't consume virtual CPU
60+
- `resources:MEMORY_MB='0'` - bare metal doesn't consume virtual memory
61+
- `resources:DISK_GB='0'` - bare metal doesn't consume virtual disk
62+
- `resources:CUSTOM_M1_SMALL='1'` - requires one bare metal node of this resource class
63+
- `trait:CUSTOM_NICX='required'` - requires the NICX trait
64+
65+
This matches the [OpenStack Ironic flavor configuration pattern](https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html) where the resource class is prefixed with `CUSTOM_`.
66+
67+
## Requirements
68+
69+
### Collections
70+
71+
- `openstack.cloud` - for `compute_flavor` module
72+
- `community.general` - for `filetree` lookup plugin
73+
74+
### ConfigMaps
75+
76+
The role expects two ConfigMaps to be mounted in the container:
77+
78+
1. **flavors** ConfigMap mounted at `/runner/data/flavors/`
79+
- Contains flavor YAML definitions
80+
- Generated from `$UC_DEPLOY/hardware/flavors/`
81+
82+
2. **device-types** ConfigMap mounted at `/runner/data/device-types/`
83+
- Contains device-type YAML definitions
84+
- Generated from `$UC_DEPLOY/hardware/device-types/`
85+
86+
### OpenStack Authentication
87+
88+
The role uses the `openstack.cloud.compute_flavor` module which requires OpenStack credentials configured in one of these ways:
89+
90+
- `clouds.yaml` file in standard locations (`/etc/openstack/`, `~/.config/openstack/`)
91+
- Environment variables (`OS_CLOUD`, `OS_AUTH_URL`, `OS_USERNAME`, etc.)
92+
93+
## Role Variables
94+
95+
### Required Variables
96+
97+
None - the role works with default values if ConfigMaps are properly mounted.
98+
99+
### Optional Variables
100+
101+
```yaml
102+
# OpenStack cloud configuration name from clouds.yaml
103+
openstack_cloud: default
104+
105+
# Path to flavors ConfigMap data
106+
flavors_configmap_path: /runner/data/flavors/
107+
108+
# Path to device-types ConfigMap data
109+
device_types_configmap_path: /runner/data/device-types/
110+
111+
# Async job timeout in seconds
112+
flavor_creation_timeout: 300
113+
114+
# Async job polling settings
115+
flavor_async_retries: 30
116+
flavor_async_delay: 5
117+
```
118+
119+
## Dependencies
120+
121+
None
122+
123+
## Example Playbook
124+
125+
```yaml
126+
---
127+
- name: Create Nova flavors from UnderStack definitions
128+
hosts: localhost
129+
gather_facts: false
130+
roles:
131+
- role: nova_flavors
132+
vars:
133+
openstack_cloud: understack
134+
```
135+
136+
## Task Breakdown
137+
138+
### main.yml
139+
140+
1. **Collect flavor definitions** - reads all YAML files from `/runner/data/flavors/`
141+
2. **Collect device-type definitions** - reads all YAML files from `/runner/data/device-types/`
142+
3. **Build resource_class mapping** - creates a lookup table mapping resource class names to their CPU/memory/disk specs
143+
4. **Loop through flavors** - processes each flavor definition
144+
5. **Wait for async jobs** - waits for all flavor creation tasks to complete
145+
146+
### nova_flavors.yml
147+
148+
For each flavor:
149+
150+
1. **Lookup resource specs** - finds the device-type resource class specifications
151+
2. **Build trait requirements** - extracts required and forbidden traits, adds `CUSTOM_` prefix
152+
3. **Build base extra_specs** - creates resource consumption specs (always 0 for bare metal) and resource class requirement
153+
4. **Build trait extra_specs** - adds trait requirements (`required` or `forbidden`)
154+
5. **Combine extra_specs** - merges all scheduling constraints
155+
6. **Create Nova flavor** - calls `openstack.cloud.compute_flavor` with derived properties
156+
7. **Track async job** - stores job ID for later status checking
157+
158+
## Trait Handling
159+
160+
The role automatically adds the `CUSTOM_` prefix to trait names:
161+
162+
**Flavor definition**:
163+
164+
```yaml
165+
traits:
166+
- trait: NICX
167+
state: required
168+
- trait: GPU
169+
state: absent
170+
```
171+
172+
**Resulting extra_specs**:
173+
174+
```yaml
175+
trait:CUSTOM_NICX: required
176+
trait:CUSTOM_GPU: forbidden
177+
```
178+
179+
This matches the [OpenStack trait naming convention](https://docs.openstack.org/placement/latest/user/index.html#traits) where custom traits must have the `CUSTOM_` prefix.
180+
181+
## Resource Class Naming
182+
183+
Resource class names are normalized to uppercase with special characters replaced by underscores:
184+
185+
- `m1.small` → `CUSTOM_M1_SMALL`
186+
- `compute-gpu` → `CUSTOM_COMPUTE_GPU`
187+
- `storage_nvme` → `CUSTOM_STORAGE_NVME`
188+
189+
This ensures valid extra_spec keys for OpenStack placement.
190+
191+
## Error Handling
192+
193+
The role will fail if:
194+
195+
- A flavor references a non-existent resource class (not found in any device-type)
196+
- Required ConfigMaps are not mounted at expected paths
197+
- OpenStack authentication fails
198+
- Flavor creation fails (invalid parameters, permissions, etc.)
199+
200+
Error messages will indicate which flavor and resource class caused the failure.
201+
202+
## Testing
203+
204+
To test the role locally:
205+
206+
1. Prepare test data directories:
207+
```bash
208+
mkdir -p /tmp/test-data/flavors /tmp/test-data/device-types
209+
cp examples/deploy-repo/hardware/flavors/*.yaml /tmp/test-data/flavors/
210+
cp examples/deploy-repo/hardware/device-types/*.yaml /tmp/test-data/device-types/
211+
```
212+
213+
2. Create a test playbook:
214+
```yaml
215+
---
216+
- name: Test nova_flavors role
217+
hosts: localhost
218+
gather_facts: false
219+
roles:
220+
- role: nova_flavors
221+
vars:
222+
flavors_configmap_path: /tmp/test-data/flavors/
223+
device_types_configmap_path: /tmp/test-data/device-types/
224+
openstack_cloud: devstack
225+
```
226+
227+
3. Run the playbook:
228+
```bash
229+
ansible-playbook test-nova-flavors.yml
230+
```
231+
232+
## Integration with UnderStack
233+
234+
This role is part of the UnderStack flavor management workflow:
235+
236+
1. **Define flavors** - operators create flavor YAML files using `understackctl flavor add`
237+
2. **GitOps deployment** - ArgoCD syncs flavor definitions to ConfigMaps
238+
3. **Trigger workflow** - flavor ConfigMap changes trigger Argo Workflow
239+
4. **Execute role** - Argo Workflow runs ansible playbook with this role
240+
5. **Create flavors** - Nova flavors are created/updated in OpenStack
241+
6. **User scheduling** - users select flavors when launching instances
242+
243+
See the [Flavors Operator Guide](https://rackerlabs.github.io/understack/operator-guide/flavors/) for the complete workflow.
244+
245+
## License
246+
247+
Apache 2.0
248+
249+
## Author Information
250+
251+
UnderStack Team - https://github.com/rackerlabs/understack
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
# Default variables for nova_flavors role
3+
4+
# OpenStack cloud configuration name from clouds.yaml
5+
# This is used by the openstack.cloud.compute_flavor module
6+
openstack_cloud: understack
7+
8+
# Path to flavors ConfigMap data (mounted in Argo Workflows)
9+
flavors_configmap_path: /runner/data/flavors/
10+
11+
# Path to device-types ConfigMap data (mounted in Argo Workflows)
12+
device_types_configmap_path: /runner/data/device-types/
13+
14+
# Async job timeout in seconds
15+
flavor_creation_timeout: 300
16+
17+
# Async job polling settings
18+
flavor_async_retries: 30
19+
flavor_async_delay: 5

0 commit comments

Comments
 (0)