Skip to content
Draft
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
1 change: 1 addition & 0 deletions docs/dictionary/en-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ env
envfile
epel
epyc
etcd
eth
extraimages
extraRPMs
Expand Down
14 changes: 14 additions & 0 deletions roles/dnsmasq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ supported in libvirt).
* `mac`: (String) Entry MAC address. Mandatory.
* `ips`: (List[string]) List of IP addresses associated to the MAC (v4, v6). Mandatory.
* `name`: (String) Host name. Optional.
* `tag`: (String) Tag to assign to this host. Tags can be used to apply specific DHCP options to groups of hosts. Optional.

#### Examples

Expand All @@ -182,7 +183,20 @@ supported in libvirt).
- "2345:0425:2CA1::0567:5673:cafe"
- "192.168.254.11"
name: r2d2
tag: droid # Optional: assign tag for DHCP options
ansible.builtin.include_role:
name: dnsmasq
tasks_from: manage_host.yml
```

#### Using tags for DHCP options

When you assign a `tag` to DHCP entries, you can then configure DHCP options for that tag:

```
# In /etc/cifmw-dnsmasq.d/custom-options.conf
dhcp-option=tag:droid,60,HTTPClient
dhcp-option=tag:droid,67,http://192.168.254.1/boot.ipxe
```

All hosts with the `droid` tag will receive these DHCP options.
119 changes: 119 additions & 0 deletions roles/dnsmasq/molecule/default/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,125 @@
name: dnsmasq
tasks_from: manage_host.yml

- name: Inject nodes with tags for DHCP options
vars:
cifmw_dnsmasq_dhcp_entries:
- network: starwars
state: present
mac: "0a:19:02:f8:4c:b1"
ips:
- "192.168.254.21"
- "2345:0425:2CA1::0567:5673:0021"
name: "r2d2"
tag: "droid"
- network: starwars
state: present
mac: "0a:19:02:f8:4c:b2"
ips:
- "192.168.254.22"
name: "c3po"
tag: "droid"
- network: startrek
state: present
mac: "0a:19:02:f8:4c:b3"
ips:
- "192.168.253.31"
name: "data"
tag: "android"
ansible.builtin.include_role:
name: dnsmasq
tasks_from: manage_host.yml

- name: Verify DHCP host entries with tags
block:
- name: Read r2d2 DHCP host entry
become: true
ansible.builtin.slurp:
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_r2d2_0a:19:02:f8:4c:b1"
register: _r2d2_entry

- name: Read c3po DHCP host entry
become: true
ansible.builtin.slurp:
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_c3po_0a:19:02:f8:4c:b2"
register: _c3po_entry

- name: Read data DHCP host entry
become: true
ansible.builtin.slurp:
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/startrek_data_0a:19:02:f8:4c:b3"
register: _data_entry

- name: Decode entries
ansible.builtin.set_fact:
_r2d2_content: "{{ _r2d2_entry.content | b64decode | trim }}"
_c3po_content: "{{ _c3po_entry.content | b64decode | trim }}"
_data_content: "{{ _data_entry.content | b64decode | trim }}"

- name: Assert r2d2 entry has droid tag
ansible.builtin.assert:
that:
- "'set:droid' in _r2d2_content"
- "'0a:19:02:f8:4c:b1' in _r2d2_content"
- "'192.168.254.21' in _r2d2_content"
- "'r2d2' in _r2d2_content"
msg: "r2d2 DHCP entry should contain tag 'droid': {{ _r2d2_content }}"

- name: Assert c3po entry has droid tag
ansible.builtin.assert:
that:
- "'set:droid' in _c3po_content"
- "'0a:19:02:f8:4c:b2' in _c3po_content"
- "'192.168.254.22' in _c3po_content"
- "'c3po' in _c3po_content"
msg: "c3po DHCP entry should contain tag 'droid': {{ _c3po_content }}"

- name: Assert data entry has android tag
ansible.builtin.assert:
that:
- "'set:android' in _data_content"
- "'0a:19:02:f8:4c:b3' in _data_content"
- "'192.168.253.31' in _data_content"
- "'data' in _data_content"
msg: "data DHCP entry should contain tag 'android': {{ _data_content }}"

- name: "Verify entry without tag has no set: prefix"
become: true
ansible.builtin.slurp:
path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_solo_0a:19:02:f8:4c:a8"
register: _solo_entry

- name: "Assert solo entry does not have a tag"
vars:
_solo_content: "{{ _solo_entry.content | b64decode | trim }}"
ansible.builtin.assert:
that:
- "'set:' not in _solo_content"
- "'0a:19:02:f8:4c:a8' in _solo_content"
- "'solo' in _solo_content"
msg: "solo DHCP entry should not contain any tag: {{ _solo_content }}"

- name: "Create DHCP options configuration for tagged hosts"
become: true
ansible.builtin.copy:
dest: "/etc/cifmw-dnsmasq.d/test-dhcp-options.conf"
content: |
# Test DHCP options for droids
dhcp-option=tag:droid,60,HTTPClient
dhcp-option=tag:droid,67,http://192.168.254.1/droid-boot.ipxe
# Test DHCP options for androids
dhcp-option=tag:android,60,HTTPClient
dhcp-option=tag:android,67,http://192.168.253.1/android-boot.ipxe
mode: '0644'
validate: "/usr/sbin/dnsmasq -C %s --test"
notify: Restart dnsmasq

- name: Verify dnsmasq configuration is valid
become: true
ansible.builtin.command:
cmd: /usr/sbin/dnsmasq -C /etc/cifmw-dnsmasq.conf --test
changed_when: false

- name: Add a domain specific forwarder
vars:
cifmw_dnsmasq_forwarder:
Expand Down
6 changes: 5 additions & 1 deletion roles/dnsmasq/tasks/manage_host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@
{%- set _ = data.append(entry.mac) -%}
{{ data | join('_') }}
_entry: >-
{% set data = [entry.mac] -%}
{% set data = [] -%}
{% if entry.tag is defined and entry.tag | length > 0 -%}
{% set _ = data.append('set:' + entry.tag) -%}
{% endif -%}
{% set _ = data.append(entry.mac) -%}
{% for ip in entry.ips if ip is not none and ip | length > 0 -%}
{% set _ = data.append(ip | ansible.utils.ipwrap) -%}
{% endfor -%}
Expand Down
161 changes: 161 additions & 0 deletions roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# DHCP Options Support in libvirt_manager

This document explains how to add DHCP options to VM groups in the libvirt_manager role.

## Overview

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.

## How It Works

1. **VM Type Tagging**: Each VM is automatically tagged with its type (e.g., `compute`, `controller`, `baremetal_instance`)
2. **DHCP Options**: You can specify DHCP options in the VM type definition
3. **dnsmasq Configuration**: The role automatically generates dnsmasq configuration that applies these options to all VMs of that type

## Configuration Example

### Basic Example

Here's how to add DHCP options for PXE booting to baremetal instances:

```yaml
cifmw_libvirt_manager_configuration:
vms:
baremetal_instance:
amount: 3
disk_file_name: "blank"
disksize: 50
memory: 8
cpus: 4
bootmenu_enable: "yes"
nets:
- public
- provisioning
dhcp_options:
- "60,HTTPClient" # Vendor class identifier
- "67,http://192.168.122.1:8081/boot.ipxe" # Boot filename (iPXE script)
```

### Advanced Example with Multiple VM Types

```yaml
cifmw_libvirt_manager_configuration:
vms:
controller:
amount: 1
image_url: "{{ cifmw_discovered_image_url }}"
sha256_image_name: "{{ cifmw_discovered_hash }}"
disk_file_name: "centos-stream-9.qcow2"
disksize: 50
memory: 4
cpus: 2
nets:
- public
- osp_trunk
# No DHCP options for controllers - they'll use defaults

compute:
amount: 3
disk_file_name: blank
disksize: 40
memory: 8
cpus: 4
nets:
- public
- osp_trunk
dhcp_options:
- "60,HTTPClient"
- "67,http://192.168.122.1:8081/boot-artifacts/compute-boot.ipxe"

baremetal_instance:
amount: 2
disk_file_name: "blank"
disksize: 50
memory: 8
cpus: 4
bootmenu_enable: "yes"
nets:
- public
dhcp_options:
- "60,HTTPClient"
- "67,http://192.168.122.1:8081/boot-artifacts/agent.x86_64.ipxe"
```

## Common DHCP Options

Here are some commonly used DHCP options for PXE/network booting:

| Option | Name | Purpose | Example |
|--------|------|---------|---------|
| 60 | vendor-class-identifier | Identifies the vendor/client type | `60,HTTPClient` |
| 67 | bootfile-name | Path to boot file | `67,http://server/boot.ipxe` |
| 66 | tftp-server-name | TFTP server address | `66,192.168.1.10` |
| 150 | tftp-server-address | TFTP server IP (Cisco) | `150,192.168.1.10` |
| 210 | path-prefix | Path prefix for boot files | `210,/tftpboot/` |


## Technical Details

### Under the Hood

1. **Tag Assignment**: When VMs are created, each is assigned a tag matching its type in the dnsmasq DHCP host entry:
```
set:baremetal_instance,52:54:00:xx:xx:xx,192.168.122.10,hostname
```

2. **DHCP Options Configuration**: A configuration file is generated at `/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf`:
```
# Options for baremetal_instance VMs
dhcp-option=tag:baremetal_instance,60,HTTPClient
dhcp-option=tag:baremetal_instance,67,http://192.168.122.1:8081/boot.ipxe
```

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.

### Files Modified

- `roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml`: Adds VM type tags to DHCP entries
- `roles/libvirt_manager/tasks/create_dhcp_options.yml`: New file that generates DHCP options configuration
- `roles/libvirt_manager/tasks/generate_networking_data.yml`: Includes the new task
- `roles/dnsmasq/tasks/manage_host.yml`: Updated to support tags in DHCP entries

## Troubleshooting

### Verify DHCP Options Are Applied

1. Check the generated configuration:
```bash
cat /etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf
```

2. Check DHCP host entries:
```bash
ls -la /etc/cifmw-dnsmasq.d/dhcp-hosts.d/
cat /etc/cifmw-dnsmasq.d/dhcp-hosts.d/public_*
```

3. Verify dnsmasq configuration is valid:
```bash
dnsmasq -C /etc/cifmw-dnsmasq.conf --test
```

4. Monitor DHCP requests:
```bash
journalctl -u cifmw-dnsmasq -f
```

### Common Issues

**Issue**: DHCP options not being sent to VMs
- **Solution**: Ensure dnsmasq service is restarted after making changes
- **Check**: Verify the VM type tag matches between the DHCP host entry and the options configuration

**Issue**: VMs not PXE booting correctly
- **Solution**: Verify the boot file URL is accessible from the VM's network
- **Check**: Ensure option 67 contains the full URL including protocol (http://)

## References

- [dnsmasq manual](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html)
- [DHCP Options RFC](https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml)
- [iPXE documentation](https://ipxe.org/howto/dhcpd)
1 change: 1 addition & 0 deletions roles/libvirt_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ cifmw_libvirt_manager_configuration:
bootmenu_enable: (string, toggle bootmenu. Optional, defaults to "no")
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)
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)
dhcp_options: (list, optional, defaults to []. List of DHCP options to apply to all VMs of this type. Format: ["option_number,value", ...])
networks:
net_name: <XML definition of the network to create>
```
Expand Down
Loading
Loading