Skip to content

Commit 71bebff

Browse files
committed
Add support for OVS as a virtual switch
To support testing the upcoming standalone networking feature it is necessary to have a virtual switch that allows port VLAN configuration to be changed by the networking generic switch driver. Setting the test_vm_bridge_type to 'ovs' will create 3 separate VLANs for testing. One will be dedicated as an inspection network, another to the final 'tenant' network for the node, and the remaining one will be used for all other network types (i.e., cleaning, rescuing, servicing, etc...). Related-Bug: 2113769 Assisted-by: Claude Code/claude-sonnet-4 Change-Id: I54b154a28dcbb4f89b368deaa7c16792630f6564 Signed-off-by: Allain Legacy <alegacy@redhat.com>
1 parent b7bb21c commit 71bebff

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)