Skip to content

Commit eb5beab

Browse files
committed
Add DHCP options support for VM types
Enable per-VM-type DHCP options in libvirt_manager for PXE boot scenarios. VMs are tagged by type and dnsmasq applies corresponding options to each group via dhcp_options field in VM definitions. Assisted-By: Claude Code/claude-sonnet-4
1 parent 696635e commit eb5beab

File tree

12 files changed

+461
-110
lines changed

12 files changed

+461
-110
lines changed

roles/dnsmasq/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ supported in libvirt).
168168
* `mac`: (String) Entry MAC address. Mandatory.
169169
* `ips`: (List[string]) List of IP addresses associated to the MAC (v4, v6). Mandatory.
170170
* `name`: (String) Host name. Optional.
171+
* `tag`: (String) Tag to assign to this host. Tags can be used to apply specific DHCP options to groups of hosts. Optional.
171172

172173
#### Examples
173174

@@ -182,7 +183,20 @@ supported in libvirt).
182183
- "2345:0425:2CA1::0567:5673:cafe"
183184
- "192.168.254.11"
184185
name: r2d2
186+
tag: droid # Optional: assign tag for DHCP options
185187
ansible.builtin.include_role:
186188
name: dnsmasq
187189
tasks_from: manage_host.yml
188190
```
191+
192+
#### Using tags for DHCP options
193+
194+
When you assign a `tag` to DHCP entries, you can then configure DHCP options for that tag:
195+
196+
```
197+
# In /etc/cifmw-dnsmasq.d/custom-options.conf
198+
dhcp-option=tag:droid,60,HTTPClient
199+
dhcp-option=tag:droid,67,http://192.168.254.1/boot.ipxe
200+
```
201+
202+
All hosts with the `droid` tag will receive these DHCP options.

roles/dnsmasq/molecule/default/converge.yml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,125 @@
145145
name: dnsmasq
146146
tasks_from: manage_host.yml
147147

148+
- name: Inject nodes with tags for DHCP options
149+
vars:
150+
cifmw_dnsmasq_dhcp_entries:
151+
- network: starwars
152+
state: present
153+
mac: "0a:19:02:f8:4c:b1"
154+
ips:
155+
- "192.168.254.21"
156+
- "2345:0425:2CA1::0567:5673:0021"
157+
name: "r2d2"
158+
tag: "droid"
159+
- network: starwars
160+
state: present
161+
mac: "0a:19:02:f8:4c:b2"
162+
ips:
163+
- "192.168.254.22"
164+
name: "c3po"
165+
tag: "droid"
166+
- network: startrek
167+
state: present
168+
mac: "0a:19:02:f8:4c:b3"
169+
ips:
170+
- "192.168.253.31"
171+
name: "data"
172+
tag: "android"
173+
ansible.builtin.include_role:
174+
name: dnsmasq
175+
tasks_from: manage_host.yml
176+
177+
- name: Verify DHCP host entries with tags
178+
block:
179+
- name: Read r2d2 DHCP host entry
180+
become: true
181+
ansible.builtin.slurp:
182+
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_r2d2_0a:19:02:f8:4c:b1"
183+
register: _r2d2_entry
184+
185+
- name: Read c3po DHCP host entry
186+
become: true
187+
ansible.builtin.slurp:
188+
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_c3po_0a:19:02:f8:4c:b2"
189+
register: _c3po_entry
190+
191+
- name: Read data DHCP host entry
192+
become: true
193+
ansible.builtin.slurp:
194+
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/startrek_data_0a:19:02:f8:4c:b3"
195+
register: _data_entry
196+
197+
- name: Decode entries
198+
ansible.builtin.set_fact:
199+
_r2d2_content: "{{ _r2d2_entry.content | b64decode | trim }}"
200+
_c3po_content: "{{ _c3po_entry.content | b64decode | trim }}"
201+
_data_content: "{{ _data_entry.content | b64decode | trim }}"
202+
203+
- name: Assert r2d2 entry has droid tag
204+
ansible.builtin.assert:
205+
that:
206+
- "'set:droid' in _r2d2_content"
207+
- "'0a:19:02:f8:4c:b1' in _r2d2_content"
208+
- "'192.168.254.21' in _r2d2_content"
209+
- "'r2d2' in _r2d2_content"
210+
msg: "r2d2 DHCP entry should contain tag 'droid': {{ _r2d2_content }}"
211+
212+
- name: Assert c3po entry has droid tag
213+
ansible.builtin.assert:
214+
that:
215+
- "'set:droid' in _c3po_content"
216+
- "'0a:19:02:f8:4c:b2' in _c3po_content"
217+
- "'192.168.254.22' in _c3po_content"
218+
- "'c3po' in _c3po_content"
219+
msg: "c3po DHCP entry should contain tag 'droid': {{ _c3po_content }}"
220+
221+
- name: Assert data entry has android tag
222+
ansible.builtin.assert:
223+
that:
224+
- "'set:android' in _data_content"
225+
- "'0a:19:02:f8:4c:b3' in _data_content"
226+
- "'192.168.253.31' in _data_content"
227+
- "'data' in _data_content"
228+
msg: "data DHCP entry should contain tag 'android': {{ _data_content }}"
229+
230+
- name: "Verify entry without tag has no set: prefix"
231+
become: true
232+
ansible.builtin.slurp:
233+
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_solo_0a:19:02:f8:4c:a8"
234+
register: _solo_entry
235+
236+
- name: "Assert solo entry does not have a tag"
237+
vars:
238+
_solo_content: "{{ _solo_entry.content | b64decode | trim }}"
239+
ansible.builtin.assert:
240+
that:
241+
- "'set:' not in _solo_content"
242+
- "'0a:19:02:f8:4c:a8' in _solo_content"
243+
- "'solo' in _solo_content"
244+
msg: "solo DHCP entry should not contain any tag: {{ _solo_content }}"
245+
246+
- name: "Create DHCP options configuration for tagged hosts"
247+
become: true
248+
ansible.builtin.copy:
249+
dest: "/etc/cifmw-dnsmasq.d/test-dhcp-options.conf"
250+
content: |
251+
# Test DHCP options for droids
252+
dhcp-option=tag:droid,60,HTTPClient
253+
dhcp-option=tag:droid,67,http://192.168.254.1/droid-boot.ipxe
254+
# Test DHCP options for androids
255+
dhcp-option=tag:android,60,HTTPClient
256+
dhcp-option=tag:android,67,http://192.168.253.1/android-boot.ipxe
257+
mode: '0644'
258+
validate: "/usr/sbin/dnsmasq -C %s --test"
259+
notify: Restart dnsmasq
260+
261+
- name: Verify dnsmasq configuration is valid
262+
become: true
263+
ansible.builtin.command:
264+
cmd: /usr/sbin/dnsmasq -C /etc/cifmw-dnsmasq.conf --test
265+
changed_when: false
266+
148267
- name: Add a domain specific forwarder
149268
vars:
150269
cifmw_dnsmasq_forwarder:

roles/dnsmasq/tasks/manage_host.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@
6262
{%- set _ = data.append(entry.mac) -%}
6363
{{ data | join('_') }}
6464
_entry: >-
65-
{% set data = [entry.mac] -%}
65+
{% set data = [] -%}
66+
{% if entry.tag is defined and entry.tag | length > 0 -%}
67+
{% set _ = data.append('set:' + entry.tag) -%}
68+
{% endif -%}
69+
{% set _ = data.append(entry.mac) -%}
6670
{% for ip in entry.ips if ip is not none and ip | length > 0 -%}
6771
{% set _ = data.append(ip | ansible.utils.ipwrap) -%}
6872
{% endfor -%}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# DHCP Options Support in libvirt_manager
2+
3+
This document explains how to add DHCP options to VM groups in the libvirt_manager role.
4+
5+
## Overview
6+
7+
The libvirt_manager role now supports assigning DHCP options to groups of VMs based on their type. This is useful for scenarios like PXE booting where you need to provide specific boot parameters to certain VM types.
8+
9+
## How It Works
10+
11+
1. **VM Type Tagging**: Each VM is automatically tagged with its type (e.g., `compute`, `controller`, `baremetal_instance`)
12+
2. **DHCP Options**: You can specify DHCP options in the VM type definition
13+
3. **dnsmasq Configuration**: The role automatically generates dnsmasq configuration that applies these options to all VMs of that type
14+
15+
## Configuration Example
16+
17+
### Basic Example
18+
19+
Here's how to add DHCP options for PXE booting to baremetal instances:
20+
21+
```yaml
22+
cifmw_libvirt_manager_configuration:
23+
vms:
24+
baremetal_instance:
25+
amount: 3
26+
disk_file_name: "blank"
27+
disksize: 50
28+
memory: 8
29+
cpus: 4
30+
bootmenu_enable: "yes"
31+
nets:
32+
- public
33+
- provisioning
34+
dhcp_options:
35+
- "60,HTTPClient" # Vendor class identifier
36+
- "67,http://192.168.122.1:8081/boot.ipxe" # Boot filename (iPXE script)
37+
```
38+
39+
### Advanced Example with Multiple VM Types
40+
41+
```yaml
42+
cifmw_libvirt_manager_configuration:
43+
vms:
44+
controller:
45+
amount: 1
46+
image_url: "{{ cifmw_discovered_image_url }}"
47+
sha256_image_name: "{{ cifmw_discovered_hash }}"
48+
disk_file_name: "centos-stream-9.qcow2"
49+
disksize: 50
50+
memory: 4
51+
cpus: 2
52+
nets:
53+
- public
54+
- osp_trunk
55+
# No DHCP options for controllers - they'll use defaults
56+
57+
compute:
58+
amount: 3
59+
disk_file_name: blank
60+
disksize: 40
61+
memory: 8
62+
cpus: 4
63+
nets:
64+
- public
65+
- osp_trunk
66+
dhcp_options:
67+
- "60,HTTPClient"
68+
- "67,http://192.168.122.1:8081/boot-artifacts/compute-boot.ipxe"
69+
70+
baremetal_instance:
71+
amount: 2
72+
disk_file_name: "blank"
73+
disksize: 50
74+
memory: 8
75+
cpus: 4
76+
bootmenu_enable: "yes"
77+
nets:
78+
- public
79+
dhcp_options:
80+
- "60,HTTPClient"
81+
- "67,http://192.168.122.1:8081/boot-artifacts/agent.x86_64.ipxe"
82+
```
83+
84+
## Common DHCP Options
85+
86+
Here are some commonly used DHCP options for PXE/network booting:
87+
88+
| Option | Name | Purpose | Example |
89+
|--------|------|---------|---------|
90+
| 60 | vendor-class-identifier | Identifies the vendor/client type | `60,HTTPClient` |
91+
| 67 | bootfile-name | Path to boot file | `67,http://server/boot.ipxe` |
92+
| 66 | tftp-server-name | TFTP server address | `66,192.168.1.10` |
93+
| 150 | tftp-server-address | TFTP server IP (Cisco) | `150,192.168.1.10` |
94+
| 210 | path-prefix | Path prefix for boot files | `210,/tftpboot/` |
95+
96+
97+
## Technical Details
98+
99+
### Under the Hood
100+
101+
1. **Tag Assignment**: When VMs are created, each is assigned a tag matching its type in the dnsmasq DHCP host entry:
102+
```
103+
set:baremetal_instance,52:54:00:xx:xx:xx,192.168.122.10,hostname
104+
```
105+
106+
2. **DHCP Options Configuration**: A configuration file is generated at `/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf`:
107+
```
108+
# Options for baremetal_instance VMs
109+
dhcp-option=tag:baremetal_instance,60,HTTPClient
110+
dhcp-option=tag:baremetal_instance,67,http://192.168.122.1:8081/boot.ipxe
111+
```
112+
113+
3. **dnsmasq Processing**: When a VM with the `baremetal_instance` tag requests DHCP, it receives both the standard network options AND the VM-type-specific options.
114+
115+
### Files Modified
116+
117+
- `roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml`: Adds VM type tags to DHCP entries
118+
- `roles/libvirt_manager/tasks/create_dhcp_options.yml`: New file that generates DHCP options configuration
119+
- `roles/libvirt_manager/tasks/generate_networking_data.yml`: Includes the new task
120+
- `roles/dnsmasq/tasks/manage_host.yml`: Updated to support tags in DHCP entries
121+
122+
## Troubleshooting
123+
124+
### Verify DHCP Options Are Applied
125+
126+
1. Check the generated configuration:
127+
```bash
128+
cat /etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf
129+
```
130+
131+
2. Check DHCP host entries:
132+
```bash
133+
ls -la /etc/cifmw-dnsmasq.d/dhcp-hosts.d/
134+
cat /etc/cifmw-dnsmasq.d/dhcp-hosts.d/public_*
135+
```
136+
137+
3. Verify dnsmasq configuration is valid:
138+
```bash
139+
dnsmasq -C /etc/cifmw-dnsmasq.conf --test
140+
```
141+
142+
4. Monitor DHCP requests:
143+
```bash
144+
journalctl -u cifmw-dnsmasq -f
145+
```
146+
147+
### Common Issues
148+
149+
**Issue**: DHCP options not being sent to VMs
150+
- **Solution**: Ensure dnsmasq service is restarted after making changes
151+
- **Check**: Verify the VM type tag matches between the DHCP host entry and the options configuration
152+
153+
**Issue**: VMs not PXE booting correctly
154+
- **Solution**: Verify the boot file URL is accessible from the VM's network
155+
- **Check**: Ensure option 67 contains the full URL including protocol (http://)
156+
157+
## References
158+
159+
- [dnsmasq manual](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html)
160+
- [DHCP Options RFC](https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml)
161+
- [iPXE documentation](https://ipxe.org/howto/dhcpd)
162+

roles/libvirt_manager/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ cifmw_libvirt_manager_configuration:
9797
bootmenu_enable: (string, toggle bootmenu. Optional, defaults to "no")
9898
networkconfig: (dict or list[dict], [network-config](https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v2.html#network-config-v2) v2 config, needed if a static ip address should be defined at boot time in absence of a dhcp server in special scenarios. Optional)
9999
devices: (dict, optional, defaults to {}. The keys are the VMs of that type that needs devices to be attached, and the values are lists of strings, where each string must contain a valid <hostdev/> libvirt XML element that will be passed to virsh attach-device)
100+
dhcp_options: (list, optional, defaults to []. List of DHCP options to apply to all VMs of this type. Format: ["option_number,value", ...])
100101
networks:
101102
net_name: <XML definition of the network to create>
102103
```

0 commit comments

Comments
 (0)