Skip to content
Merged
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
14 changes: 14 additions & 0 deletions doc/source/contributor/testenv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,17 @@ sushy-tools_ is also installed.

.. _VirtualBMC: https://docs.openstack.org/virtualbmc/
.. _sushy-tools: https://docs.openstack.org/sushy-tools/

Virtual Switching
-----------------
By default, Bifrost sets up a Linux bridge as the virtual switch
interconnecting the virtual machines that implement the nodes. To support
more complex test scenarios, it is possible to configure OVS as the virtual
switch. This enables updates to port VLAN assignments to test complex
networking scenarios.

The virtual switch type can be controlled by modifying the
``test_vm_switch_type`` variable via ansible extra vars supplied to the Ansible
commands or via bifrost-cli's ``-e`` option. Setting the variable to 'ovs'
enables the OVS switch type.

44 changes: 44 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ The following packages are required and ensured to be present:
- qemu-kvm
- sgabios (except on CentOS Stream 10 / Rocky Linux 10)

Additional packages required when using test_vm_switch_type: 'ovs':
- openvswitch-switch (Debian/Ubuntu)
- openvswitch (RedHat/CentOS)


Warning
-------
Expand Down Expand Up @@ -150,6 +154,31 @@ test_vm_network_dhcp_end: End of DHCP range for 'test_vm_network'.
from scratch and when
'test_vm_network_enable_dhcp' is enabled.

test_vm_switch_type: Type of virtual switch to use for test VMs.
Defaults to 'linux_bridge'.
Set to 'ovs' to use Open vSwitch with VLAN support
for testing networking features.

test_ovs_bridge_name: Name of the OVS bridge to create when using
test_vm_switch_type: 'ovs'.
Defaults to 'brtest'.

test_ovs_host_vlans: List of VLAN IDs to configure on the OVS bridge.
Defaults to ['10', '20', '30'].
Creates separate VLANs for inspection, tenant, and
other network types (cleaning, rescuing, servicing).
VLAN IDs must be 1-255.

test_ovs_vm_initial_vlan: Initial VLAN ID for test VMs on OVS bridge.
Defaults to '10'.
VMs start on this VLAN and can be moved between
VLANs by the networking driver.

test_ovs_user: Username for OVS restricted user access.
Defaults to 'ovsuser'.
Uses SSH key-based authentication (password login is disabled).
Used for controlled VLAN management operations.

Dependencies
------------

Expand All @@ -158,12 +187,27 @@ None at this time.
Example Playbook
----------------

Basic usage with default Linux bridge:

- hosts: localhost
connection: local
become: yes
gather_facts: yes
roles:
- role: bifrost-create-vm-nodes

Using Open vSwitch for testing standalone networking features:

- hosts: localhost
connection: local
become: yes
gather_facts: yes
roles:
- role: bifrost-create-vm-nodes
vars:
test_vm_switch_type: ovs
test_ovs_host_vlans: ['10', '20', '30']
test_ovs_vm_initial_vlan: '10'

License
-------
Expand Down
15 changes: 15 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,18 @@ efi_nvram_locations_secboot:
- /usr/share/OVMF/OVMF_VARS.secboot.fd
efi_nvram_locations: >-
{{ efi_nvram_locations_secboot if test_vm_secure_boot | bool else efi_nvram_locations_normal }}

# Switch type configuration (default: linux_bridge)
test_vm_switch_type: linux_bridge

# OVS-specific configuration
test_ovs_bridge_name: brtest

# Simple VLAN configuration
# NOTE: VLAN IDs must be 1-255 when used for IP subnets (192.168.{VLAN}.0/24)
test_ovs_host_vlans: ['10', '20', '30']
test_ovs_vm_initial_vlan: '10'

# OVS restricted user configuration
# Uses SSH key-based authentication (password authentication is disabled)
test_ovs_user: ovsuser
17 changes: 17 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
- name: Restart sshd
systemd:
name: sshd
state: restarted
45 changes: 41 additions & 4 deletions playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
vm_name: "{{ item }}"
vm_log_file: "{{ test_vm_logdir }}/{{ item }}_console.log"
vm_host_group: "{{ test_vm_default_groups }}"
vm_port_name: "{{ item }}-port0"

- set_fact:
vm_host_group: "{{ test_vm_default_groups | union(test_vm_groups[vm_name]) }}"
Expand All @@ -41,6 +42,28 @@
command: list_vms
register: existing_vms

# Create OVS port with VLAN tag for VM when using OVS
- name: check if OVS port already exists
shell:
cmd: |
set -eo pipefail
ovs-vsctl list-ports {{ test_ovs_bridge_name }} | grep -x {{ vm_port_name }} || echo "not_found"
register: ovs_port_check
when: test_vm_switch_type == 'ovs'

- name: create OVS port with VLAN tag
shell: |
ovs-vsctl add-port {{ test_ovs_bridge_name }} {{ vm_port_name }} tag={{ test_ovs_vm_initial_vlan }} -- set interface {{ vm_port_name }} type=internal
when:
- test_vm_switch_type == 'ovs'
- ovs_port_check.stdout.strip() == "not_found"

- name: configure OVS linux interface
shell: |
ovs-vsctl set interface {{ vm_port_name }} lldp:enable=true
ip link set {{ vm_port_name }} up
when: test_vm_switch_type == 'ovs'

# NOTE(pas-ha) wrapping in block/rescue to have diagnostic output, requires Ansible>=2
- when: vm_name not in existing_vms.list_vms
block:
Expand Down Expand Up @@ -129,6 +152,20 @@
set_fact:
vm_mac: "{{ (testvm_xml.get_xml | regex_findall(\"<mac address='.*'/>\") | first).split('=') | last | regex_replace(\"['/>]\", '') }}"

- name: set VM network configuration for OVS
set_fact:
vm_network_base: "192.168.{{ 100 + test_ovs_vm_initial_vlan | int }}."
vm_ip_offset: "{{ 2 + (testvm_json_data | length) }}"
mgmt_network_ip: "192.168.{{ 100 + test_ovs_vm_initial_vlan | int }}.1"
when: test_vm_switch_type == 'ovs'

- name: set VM network configuration for bridge
set_fact:
vm_network_base: "192.168.122."
vm_ip_offset: "{{ 2 + (testvm_json_data | length) }}"
mgmt_network_ip: "192.168.122.1"
when: test_vm_switch_type == 'linux_bridge'

# NOTE(pas-ha) using default username and password set by virtualbmc - "admin" and "password" respectively
# see vbmc add --help
- name: set the json entry for vm
Expand All @@ -139,7 +176,7 @@
host_groups: "{{ vm_host_group }}"
driver: "{{ test_vm_node_driver }}"
driver_info:
ipmi_address: "192.168.122.1"
ipmi_address: "{{ mgmt_network_ip }}"
ipmi_port: "{{ virtual_ipmi_port }}"
ipmi_username: "admin"
ipmi_password: "password"
Expand All @@ -149,8 +186,8 @@
redfish_password: "password"
nics:
- mac: "{{ vm_mac }}"
ansible_ssh_host: "192.168.122.{{ testvm_json_data | length + 2 }}"
ipv4_address: "192.168.122.{{ testvm_json_data | length + 2 }}"
ansible_ssh_host: "{{ vm_network_base }}{{ vm_ip_offset }}"
ipv4_address: "{{ vm_network_base }}{{ vm_ip_offset }}"
properties:
cpu_arch: "{{ test_vm_arch }}"
ram: "{{ test_vm_memory_size }}"
Expand All @@ -161,7 +198,7 @@
uuid: "{{ vm_name | to_uuid }}"
driver: "{{ test_vm_node_driver }}"
driver_info:
ipmi_address: "192.168.122.1"
ipmi_address: "{{ mgmt_network_ip }}"
ipmi_port: "{{ virtual_ipmi_port }}"
ipmi_username: "admin"
ipmi_password: "password"
Expand Down
3 changes: 3 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
group: "{{ ansible_user_gid }}"
when: copy_from_local_path | bool

- import_tasks: prepare_ovs.yml
when: test_vm_switch_type == 'ovs'

- import_tasks: prepare_libvirt.yml

- name: truncate explicit list of vm names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
virt_net:
name: "{{ test_vm_network }}"
state: present
xml: "{{ lookup('template', 'net.xml.j2') }}"
xml: "{{ lookup('template', 'ovs-net.xml.j2' if test_vm_switch_type == 'ovs' else 'net.xml.j2') }}"
uri: "{{ test_vm_libvirt_uri }}"

- name: find facts on libvirt networks
Expand Down
120 changes: 120 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_ovs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Setup OVS bridge with VLAN interfaces and DHCP services
---
- name: enable NFV repository for OVS on CentOS Stream 10
package:
name: centos-release-nfv-openvswitch
state: present
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version|int >= 10

- name: install OVS packages
package:
name: "{{ ovs_packages }}"
state: present

- name: ensure OVS services are started and enabled
systemd:
name: "{{ ovs_service_name }}"
state: started
enabled: yes

- name: create OVS bridge
openvswitch.openvswitch.openvswitch_bridge:
bridge: "{{ test_ovs_bridge_name }}"
state: present

- name: bring up OVS bridge
command: ip link set {{ test_ovs_bridge_name }} up

- name: create VLAN interfaces on OVS bridge
shell: |
ovs-vsctl add-port {{ test_ovs_bridge_name }} {{ test_ovs_bridge_name }}.{{ item }} tag={{ item }} -- set interface {{ test_ovs_bridge_name }}.{{ item }} type=internal
ip addr add 192.168.{{ 100 + item | int }}.1/24 dev {{ test_ovs_bridge_name }}.{{ item }}
ip link set {{ test_ovs_bridge_name }}.{{ item }} up
loop: "{{ test_ovs_host_vlans }}"
ignore_errors: yes

- name: enable IP forwarding for OVS bridge
sysctl:
name: "net.ipv4.ip_forward"
value: 1
sysctl_set: yes
state: present
reload: yes

- name: ensure .ssh directory exists for OVS user
file:
path: /home/{{ test_ovs_user }}/.ssh
state: directory
owner: "{{ test_ovs_user }}"
mode: '0700'

- name: create OVS user
user:
name: "{{ test_ovs_user }}"
password: '!' # Disabled password
shell: /bin/bash
home: /home/{{ test_ovs_user }}
create_home: yes
groups: openvswitch
state: present

- name: generate SSH key pair for OVS user
user:
name: "{{ test_ovs_user }}"
generate_ssh_key: yes
ssh_key_type: ed25519
ssh_key_file: .ssh/id_ed25519

- name: read OVS user public key
slurp:
src: /home/{{ test_ovs_user }}/.ssh/id_ed25519.pub
register: ovs_user_pubkey

- name: add public key to authorized_keys for OVS user
authorized_key:
user: "{{ test_ovs_user }}"
key: "{{ ovs_user_pubkey['content'] | b64decode }}"
state: present

- name: set OVS socket group permissions
file:
path: /var/run/openvswitch/db.sock
group: openvswitch
mode: '0660'

# TODO(alegacy): this could be refined so that access is restricted to a
# specific set of OVS commands only using something like rbash
- name: add OVS user to sudoers for privileged access
copy:
dest: /etc/sudoers.d/{{ test_ovs_user }}-ovs
mode: '0440'
content: |
# Allow {{ test_ovs_user }} to run OVS commands as root without password
{{ test_ovs_user }} ALL=(ALL) NOPASSWD: /bin/bash

- name: Restrict OVS user SSH access from localhost only and disable password auth
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config
block: |
Match User {{ test_ovs_user }}
AllowUsers {{ test_ovs_user }}@localhost {{ test_ovs_user }}@127.0.0.1 {{ test_ovs_user }}@::1
PasswordAuthentication no
PubkeyAuthentication yes
marker: "# {mark} ANSIBLE MANAGED BLOCK FOR {{ test_ovs_user }}"
validate: 'sshd -t -f %s'
notify: Restart sshd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<network>
<name>{{ test_vm_network }}</name>
<forward mode='bridge'/>
<bridge name='{{ test_ovs_bridge_name }}'/>
<virtualport type='openvswitch'/>
</network>
11 changes: 11 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,24 @@
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
</disk>
<controller type='sata' index='0'/>
{% if test_vm_switch_type == 'ovs' %}
<interface type='direct'>
<source dev='{{ vm_port_name }}' mode='passthrough'/>
<virtualport type='openvswitch'/>
<model type='{{ test_vm_nic }}'/>
{% if default_boot_mode == 'uefi' %}
<boot order='1'/>
{% endif %}
</interface>
{% else %}
<interface type='network'>
<source network='{{ test_vm_network }}'/>
<model type='{{ test_vm_nic }}'/>
{% if default_boot_mode == 'uefi' %}
<boot order='1'/>
{% endif %}
</interface>
{% endif %}
<input type='mouse' bus='ps2'/>
<serial type='file'>
<source path='{{ vm_log_file }}'/>
Expand Down
5 changes: 5 additions & 0 deletions playbooks/roles/bifrost-create-vm-nodes/vars/debian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ required_packages:
- ovmf
- ebtables
- dnsmasq

ovs_packages:
- openvswitch-switch
- python3-openvswitch
ovs_service_name: openvswitch-switch
Loading