Skip to content

Commit 0c50782

Browse files
authored
Merge pull request #99 from stackhpc/upstream/master-2025-12-01
Synchronise master with upstream
2 parents 9791fbb + 71bebff commit 0c50782

File tree

19 files changed

+396
-6
lines changed

19 files changed

+396
-6
lines changed

doc/source/contributor/testenv.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,17 @@ sushy-tools_ is also installed.
188188

189189
.. _VirtualBMC: https://docs.openstack.org/virtualbmc/
190190
.. _sushy-tools: https://docs.openstack.org/sushy-tools/
191+
192+
Virtual Switching
193+
-----------------
194+
By default, Bifrost sets up a Linux bridge as the virtual switch
195+
interconnecting the virtual machines that implement the nodes. To support
196+
more complex test scenarios, it is possible to configure OVS as the virtual
197+
switch. This enables updates to port VLAN assignments to test complex
198+
networking scenarios.
199+
200+
The virtual switch type can be controlled by modifying the
201+
``test_vm_switch_type`` variable via ansible extra vars supplied to the Ansible
202+
commands or via bifrost-cli's ``-e`` option. Setting the variable to 'ovs'
203+
enables the OVS switch type.
204+

playbooks/roles/bifrost-create-vm-nodes/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ The following packages are required and ensured to be present:
1212
- qemu-kvm
1313
- sgabios (except on CentOS Stream 10 / Rocky Linux 10)
1414

15+
Additional packages required when using test_vm_switch_type: 'ovs':
16+
- openvswitch-switch (Debian/Ubuntu)
17+
- openvswitch (RedHat/CentOS)
18+
1519

1620
Warning
1721
-------
@@ -150,6 +154,31 @@ test_vm_network_dhcp_end: End of DHCP range for 'test_vm_network'.
150154
from scratch and when
151155
'test_vm_network_enable_dhcp' is enabled.
152156

157+
test_vm_switch_type: Type of virtual switch to use for test VMs.
158+
Defaults to 'linux_bridge'.
159+
Set to 'ovs' to use Open vSwitch with VLAN support
160+
for testing networking features.
161+
162+
test_ovs_bridge_name: Name of the OVS bridge to create when using
163+
test_vm_switch_type: 'ovs'.
164+
Defaults to 'brtest'.
165+
166+
test_ovs_host_vlans: List of VLAN IDs to configure on the OVS bridge.
167+
Defaults to ['10', '20', '30'].
168+
Creates separate VLANs for inspection, tenant, and
169+
other network types (cleaning, rescuing, servicing).
170+
VLAN IDs must be 1-255.
171+
172+
test_ovs_vm_initial_vlan: Initial VLAN ID for test VMs on OVS bridge.
173+
Defaults to '10'.
174+
VMs start on this VLAN and can be moved between
175+
VLANs by the networking driver.
176+
177+
test_ovs_user: Username for OVS restricted user access.
178+
Defaults to 'ovsuser'.
179+
Uses SSH key-based authentication (password login is disabled).
180+
Used for controlled VLAN management operations.
181+
153182
Dependencies
154183
------------
155184

@@ -158,12 +187,27 @@ None at this time.
158187
Example Playbook
159188
----------------
160189

190+
Basic usage with default Linux bridge:
191+
192+
- hosts: localhost
193+
connection: local
194+
become: yes
195+
gather_facts: yes
196+
roles:
197+
- role: bifrost-create-vm-nodes
198+
199+
Using Open vSwitch for testing standalone networking features:
200+
161201
- hosts: localhost
162202
connection: local
163203
become: yes
164204
gather_facts: yes
165205
roles:
166206
- role: bifrost-create-vm-nodes
207+
vars:
208+
test_vm_switch_type: ovs
209+
test_ovs_host_vlans: ['10', '20', '30']
210+
test_ovs_vm_initial_vlan: '10'
167211

168212
License
169213
-------

playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,18 @@ efi_nvram_locations_secboot:
9797
- /usr/share/OVMF/OVMF_VARS.secboot.fd
9898
efi_nvram_locations: >-
9999
{{ efi_nvram_locations_secboot if test_vm_secure_boot | bool else efi_nvram_locations_normal }}
100+
101+
# Switch type configuration (default: linux_bridge)
102+
test_vm_switch_type: linux_bridge
103+
104+
# OVS-specific configuration
105+
test_ovs_bridge_name: brtest
106+
107+
# Simple VLAN configuration
108+
# NOTE: VLAN IDs must be 1-255 when used for IP subnets (192.168.{VLAN}.0/24)
109+
test_ovs_host_vlans: ['10', '20', '30']
110+
test_ovs_vm_initial_vlan: '10'
111+
112+
# OVS restricted user configuration
113+
# Uses SSH key-based authentication (password authentication is disabled)
114+
test_ovs_user: ovsuser
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10+
# implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
---
14+
- name: Restart sshd
15+
systemd:
16+
name: sshd
17+
state: restarted

playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
vm_name: "{{ item }}"
2020
vm_log_file: "{{ test_vm_logdir }}/{{ item }}_console.log"
2121
vm_host_group: "{{ test_vm_default_groups }}"
22+
vm_port_name: "{{ item }}-port0"
2223

2324
- set_fact:
2425
vm_host_group: "{{ test_vm_default_groups | union(test_vm_groups[vm_name]) }}"
@@ -41,6 +42,28 @@
4142
command: list_vms
4243
register: existing_vms
4344

45+
# Create OVS port with VLAN tag for VM when using OVS
46+
- name: check if OVS port already exists
47+
shell:
48+
cmd: |
49+
set -eo pipefail
50+
ovs-vsctl list-ports {{ test_ovs_bridge_name }} | grep -x {{ vm_port_name }} || echo "not_found"
51+
register: ovs_port_check
52+
when: test_vm_switch_type == 'ovs'
53+
54+
- name: create OVS port with VLAN tag
55+
shell: |
56+
ovs-vsctl add-port {{ test_ovs_bridge_name }} {{ vm_port_name }} tag={{ test_ovs_vm_initial_vlan }} -- set interface {{ vm_port_name }} type=internal
57+
when:
58+
- test_vm_switch_type == 'ovs'
59+
- ovs_port_check.stdout.strip() == "not_found"
60+
61+
- name: configure OVS linux interface
62+
shell: |
63+
ovs-vsctl set interface {{ vm_port_name }} lldp:enable=true
64+
ip link set {{ vm_port_name }} up
65+
when: test_vm_switch_type == 'ovs'
66+
4467
# NOTE(pas-ha) wrapping in block/rescue to have diagnostic output, requires Ansible>=2
4568
- when: vm_name not in existing_vms.list_vms
4669
block:
@@ -129,6 +152,20 @@
129152
set_fact:
130153
vm_mac: "{{ (testvm_xml.get_xml | regex_findall(\"<mac address='.*'/>\") | first).split('=') | last | regex_replace(\"['/>]\", '') }}"
131154

155+
- name: set VM network configuration for OVS
156+
set_fact:
157+
vm_network_base: "192.168.{{ 100 + test_ovs_vm_initial_vlan | int }}."
158+
vm_ip_offset: "{{ 2 + (testvm_json_data | length) }}"
159+
mgmt_network_ip: "192.168.{{ 100 + test_ovs_vm_initial_vlan | int }}.1"
160+
when: test_vm_switch_type == 'ovs'
161+
162+
- name: set VM network configuration for bridge
163+
set_fact:
164+
vm_network_base: "192.168.122."
165+
vm_ip_offset: "{{ 2 + (testvm_json_data | length) }}"
166+
mgmt_network_ip: "192.168.122.1"
167+
when: test_vm_switch_type == 'linux_bridge'
168+
132169
# NOTE(pas-ha) using default username and password set by virtualbmc - "admin" and "password" respectively
133170
# see vbmc add --help
134171
- name: set the json entry for vm
@@ -139,7 +176,7 @@
139176
host_groups: "{{ vm_host_group }}"
140177
driver: "{{ test_vm_node_driver }}"
141178
driver_info:
142-
ipmi_address: "192.168.122.1"
179+
ipmi_address: "{{ mgmt_network_ip }}"
143180
ipmi_port: "{{ virtual_ipmi_port }}"
144181
ipmi_username: "admin"
145182
ipmi_password: "password"
@@ -149,8 +186,8 @@
149186
redfish_password: "password"
150187
nics:
151188
- mac: "{{ vm_mac }}"
152-
ansible_ssh_host: "192.168.122.{{ testvm_json_data | length + 2 }}"
153-
ipv4_address: "192.168.122.{{ testvm_json_data | length + 2 }}"
189+
ansible_ssh_host: "{{ vm_network_base }}{{ vm_ip_offset }}"
190+
ipv4_address: "{{ vm_network_base }}{{ vm_ip_offset }}"
154191
properties:
155192
cpu_arch: "{{ test_vm_arch }}"
156193
ram: "{{ test_vm_memory_size }}"
@@ -161,7 +198,7 @@
161198
uuid: "{{ vm_name | to_uuid }}"
162199
driver: "{{ test_vm_node_driver }}"
163200
driver_info:
164-
ipmi_address: "192.168.122.1"
201+
ipmi_address: "{{ mgmt_network_ip }}"
165202
ipmi_port: "{{ virtual_ipmi_port }}"
166203
ipmi_username: "admin"
167204
ipmi_password: "password"

playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@
8585
group: "{{ ansible_user_gid }}"
8686
when: copy_from_local_path | bool
8787

88+
- import_tasks: prepare_ovs.yml
89+
when: test_vm_switch_type == 'ovs'
90+
8891
- import_tasks: prepare_libvirt.yml
8992

9093
- name: truncate explicit list of vm names

playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_libvirt.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
virt_net:
105105
name: "{{ test_vm_network }}"
106106
state: present
107-
xml: "{{ lookup('template', 'net.xml.j2') }}"
107+
xml: "{{ lookup('template', 'ovs-net.xml.j2' if test_vm_switch_type == 'ovs' else 'net.xml.j2') }}"
108108
uri: "{{ test_vm_libvirt_uri }}"
109109

110110
- name: find facts on libvirt networks
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10+
# implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
# Setup OVS bridge with VLAN interfaces and DHCP services
15+
---
16+
- name: enable NFV repository for OVS on CentOS Stream 10
17+
package:
18+
name: centos-release-nfv-openvswitch
19+
state: present
20+
when:
21+
- ansible_distribution == "CentOS"
22+
- ansible_distribution_major_version|int >= 10
23+
24+
- name: install OVS packages
25+
package:
26+
name: "{{ ovs_packages }}"
27+
state: present
28+
29+
- name: ensure OVS services are started and enabled
30+
systemd:
31+
name: "{{ ovs_service_name }}"
32+
state: started
33+
enabled: yes
34+
35+
- name: create OVS bridge
36+
openvswitch.openvswitch.openvswitch_bridge:
37+
bridge: "{{ test_ovs_bridge_name }}"
38+
state: present
39+
40+
- name: bring up OVS bridge
41+
command: ip link set {{ test_ovs_bridge_name }} up
42+
43+
- name: create VLAN interfaces on OVS bridge
44+
shell: |
45+
ovs-vsctl add-port {{ test_ovs_bridge_name }} {{ test_ovs_bridge_name }}.{{ item }} tag={{ item }} -- set interface {{ test_ovs_bridge_name }}.{{ item }} type=internal
46+
ip addr add 192.168.{{ 100 + item | int }}.1/24 dev {{ test_ovs_bridge_name }}.{{ item }}
47+
ip link set {{ test_ovs_bridge_name }}.{{ item }} up
48+
loop: "{{ test_ovs_host_vlans }}"
49+
ignore_errors: yes
50+
51+
- name: enable IP forwarding for OVS bridge
52+
sysctl:
53+
name: "net.ipv4.ip_forward"
54+
value: 1
55+
sysctl_set: yes
56+
state: present
57+
reload: yes
58+
59+
- name: ensure .ssh directory exists for OVS user
60+
file:
61+
path: /home/{{ test_ovs_user }}/.ssh
62+
state: directory
63+
owner: "{{ test_ovs_user }}"
64+
mode: '0700'
65+
66+
- name: create OVS user
67+
user:
68+
name: "{{ test_ovs_user }}"
69+
password: '!' # Disabled password
70+
shell: /bin/bash
71+
home: /home/{{ test_ovs_user }}
72+
create_home: yes
73+
groups: openvswitch
74+
state: present
75+
76+
- name: generate SSH key pair for OVS user
77+
user:
78+
name: "{{ test_ovs_user }}"
79+
generate_ssh_key: yes
80+
ssh_key_type: ed25519
81+
ssh_key_file: .ssh/id_ed25519
82+
83+
- name: read OVS user public key
84+
slurp:
85+
src: /home/{{ test_ovs_user }}/.ssh/id_ed25519.pub
86+
register: ovs_user_pubkey
87+
88+
- name: add public key to authorized_keys for OVS user
89+
authorized_key:
90+
user: "{{ test_ovs_user }}"
91+
key: "{{ ovs_user_pubkey['content'] | b64decode }}"
92+
state: present
93+
94+
- name: set OVS socket group permissions
95+
file:
96+
path: /var/run/openvswitch/db.sock
97+
group: openvswitch
98+
mode: '0660'
99+
100+
# TODO(alegacy): this could be refined so that access is restricted to a
101+
# specific set of OVS commands only using something like rbash
102+
- name: add OVS user to sudoers for privileged access
103+
copy:
104+
dest: /etc/sudoers.d/{{ test_ovs_user }}-ovs
105+
mode: '0440'
106+
content: |
107+
# Allow {{ test_ovs_user }} to run OVS commands as root without password
108+
{{ test_ovs_user }} ALL=(ALL) NOPASSWD: /bin/bash
109+
110+
- name: Restrict OVS user SSH access from localhost only and disable password auth
111+
ansible.builtin.blockinfile:
112+
path: /etc/ssh/sshd_config
113+
block: |
114+
Match User {{ test_ovs_user }}
115+
AllowUsers {{ test_ovs_user }}@localhost {{ test_ovs_user }}@127.0.0.1 {{ test_ovs_user }}@::1
116+
PasswordAuthentication no
117+
PubkeyAuthentication yes
118+
marker: "# {mark} ANSIBLE MANAGED BLOCK FOR {{ test_ovs_user }}"
119+
validate: 'sshd -t -f %s'
120+
notify: Restart sshd
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<network>
2+
<name>{{ test_vm_network }}</name>
3+
<forward mode='bridge'/>
4+
<bridge name='{{ test_ovs_bridge_name }}'/>
5+
<virtualport type='openvswitch'/>
6+
</network>

playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,24 @@
3636
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
3737
</disk>
3838
<controller type='sata' index='0'/>
39+
{% if test_vm_switch_type == 'ovs' %}
40+
<interface type='direct'>
41+
<source dev='{{ vm_port_name }}' mode='passthrough'/>
42+
<virtualport type='openvswitch'/>
43+
<model type='{{ test_vm_nic }}'/>
44+
{% if default_boot_mode == 'uefi' %}
45+
<boot order='1'/>
46+
{% endif %}
47+
</interface>
48+
{% else %}
3949
<interface type='network'>
4050
<source network='{{ test_vm_network }}'/>
4151
<model type='{{ test_vm_nic }}'/>
4252
{% if default_boot_mode == 'uefi' %}
4353
<boot order='1'/>
4454
{% endif %}
4555
</interface>
56+
{% endif %}
4657
<input type='mouse' bus='ps2'/>
4758
<serial type='file'>
4859
<source path='{{ vm_log_file }}'/>

0 commit comments

Comments
 (0)