From 71bebff81ec43ea1354603501dc23d6a9e824769 Mon Sep 17 00:00:00 2001 From: Allain Legacy Date: Fri, 12 Sep 2025 10:16:17 -0400 Subject: [PATCH] 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 --- doc/source/contributor/testenv.rst | 14 ++ .../roles/bifrost-create-vm-nodes/README.md | 44 +++++++ .../bifrost-create-vm-nodes/defaults/main.yml | 15 +++ .../bifrost-create-vm-nodes/handlers/main.yml | 17 +++ .../tasks/create_vm.yml | 45 ++++++- .../bifrost-create-vm-nodes/tasks/main.yml | 3 + .../tasks/prepare_libvirt.yml | 2 +- .../tasks/prepare_ovs.yml | 120 ++++++++++++++++++ .../templates/ovs-net.xml.j2 | 6 + .../templates/testvm.xml.j2 | 11 ++ .../bifrost-create-vm-nodes/vars/debian.yml | 5 + .../bifrost-create-vm-nodes/vars/redhat.yml | 7 + .../bifrost-ironic-install/defaults/main.yml | 4 + .../tasks/bootstrap.yml | 14 +- .../bifrost-ironic-install/tasks/main.yml | 7 + .../tasks/ovs_bootstrap.yml | 62 +++++++++ .../templates/dnsmasq.conf.j2 | 4 + .../templates/ovs-vlans-dhcp.conf.j2 | 12 ++ ...rtual-switch-support-36b3693a1076e2f6.yaml | 10 ++ 19 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 playbooks/roles/bifrost-create-vm-nodes/handlers/main.yml create mode 100644 playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_ovs.yml create mode 100644 playbooks/roles/bifrost-create-vm-nodes/templates/ovs-net.xml.j2 create mode 100644 playbooks/roles/bifrost-ironic-install/tasks/ovs_bootstrap.yml create mode 100644 playbooks/roles/bifrost-ironic-install/templates/ovs-vlans-dhcp.conf.j2 create mode 100644 releasenotes/notes/ovs-virtual-switch-support-36b3693a1076e2f6.yaml diff --git a/doc/source/contributor/testenv.rst b/doc/source/contributor/testenv.rst index 38d9be4b0..60046cba5 100644 --- a/doc/source/contributor/testenv.rst +++ b/doc/source/contributor/testenv.rst @@ -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. + diff --git a/playbooks/roles/bifrost-create-vm-nodes/README.md b/playbooks/roles/bifrost-create-vm-nodes/README.md index 31812c9aa..b37e333e8 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/README.md +++ b/playbooks/roles/bifrost-create-vm-nodes/README.md @@ -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 ------- @@ -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 ------------ @@ -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 ------- diff --git a/playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml b/playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml index e95da717c..d2021fc50 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/defaults/main.yml @@ -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 diff --git a/playbooks/roles/bifrost-create-vm-nodes/handlers/main.yml b/playbooks/roles/bifrost-create-vm-nodes/handlers/main.yml new file mode 100644 index 000000000..9610f837e --- /dev/null +++ b/playbooks/roles/bifrost-create-vm-nodes/handlers/main.yml @@ -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 diff --git a/playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml b/playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml index 54b7e0715..9e87f8451 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/tasks/create_vm.yml @@ -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]) }}" @@ -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: @@ -129,6 +152,20 @@ set_fact: vm_mac: "{{ (testvm_xml.get_xml | regex_findall(\"\") | 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 @@ -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" @@ -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 }}" @@ -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" diff --git a/playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml b/playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml index 6efb1bc2d..c8b1c44fa 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/tasks/main.yml @@ -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 diff --git a/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_libvirt.yml b/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_libvirt.yml index f5b49c1fd..aaf6e0290 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_libvirt.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_libvirt.yml @@ -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 diff --git a/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_ovs.yml b/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_ovs.yml new file mode 100644 index 000000000..e58db8601 --- /dev/null +++ b/playbooks/roles/bifrost-create-vm-nodes/tasks/prepare_ovs.yml @@ -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 diff --git a/playbooks/roles/bifrost-create-vm-nodes/templates/ovs-net.xml.j2 b/playbooks/roles/bifrost-create-vm-nodes/templates/ovs-net.xml.j2 new file mode 100644 index 000000000..c54ec4253 --- /dev/null +++ b/playbooks/roles/bifrost-create-vm-nodes/templates/ovs-net.xml.j2 @@ -0,0 +1,6 @@ + + {{ test_vm_network }} + + + + diff --git a/playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2 b/playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2 index 150e7591d..310edcb18 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2 +++ b/playbooks/roles/bifrost-create-vm-nodes/templates/testvm.xml.j2 @@ -36,6 +36,16 @@
+ {% if test_vm_switch_type == 'ovs' %} + + + + + {% if default_boot_mode == 'uefi' %} + + {% endif %} + + {% else %} @@ -43,6 +53,7 @@ {% endif %} + {% endif %} diff --git a/playbooks/roles/bifrost-create-vm-nodes/vars/debian.yml b/playbooks/roles/bifrost-create-vm-nodes/vars/debian.yml index e0ac86e7c..dfb8814ba 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/vars/debian.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/vars/debian.yml @@ -15,3 +15,8 @@ required_packages: - ovmf - ebtables - dnsmasq + +ovs_packages: + - openvswitch-switch + - python3-openvswitch +ovs_service_name: openvswitch-switch diff --git a/playbooks/roles/bifrost-create-vm-nodes/vars/redhat.yml b/playbooks/roles/bifrost-create-vm-nodes/vars/redhat.yml index 6e118e760..dade70b1e 100644 --- a/playbooks/roles/bifrost-create-vm-nodes/vars/redhat.yml +++ b/playbooks/roles/bifrost-create-vm-nodes/vars/redhat.yml @@ -16,4 +16,11 @@ required_packages: - libxslt-devel - libxml2-devel - edk2-ovmf + +ovs_packages: + # TODO(alegacy): Update versioned package names periodically + - openvswitch3.5 + - python3-openvswitch3.5 +ovs_service_name: openvswitch + test_vm_emulator: "/usr/libexec/qemu-kvm" diff --git a/playbooks/roles/bifrost-ironic-install/defaults/main.yml b/playbooks/roles/bifrost-ironic-install/defaults/main.yml index 2f2cb8588..975d69154 100644 --- a/playbooks/roles/bifrost-ironic-install/defaults/main.yml +++ b/playbooks/roles/bifrost-ironic-install/defaults/main.yml @@ -409,6 +409,10 @@ keystone: # Timeout for gathering facts. fact_gather_timeout: "{{ lookup('config', 'DEFAULT_GATHER_TIMEOUT', on_missing='skip') | default(omit, true) }}" +# Custom switch support (duplicated from create-vm-nodes) +test_vm_switch_type: linux_bridge +test_ovs_bridge_name: brtest + # Enable TLS support. enable_tls: false vmedia_enable_tls: "{{ enable_tls }}" diff --git a/playbooks/roles/bifrost-ironic-install/tasks/bootstrap.yml b/playbooks/roles/bifrost-ironic-install/tasks/bootstrap.yml index 29a230a52..83454e038 100644 --- a/playbooks/roles/bifrost-ironic-install/tasks/bootstrap.yml +++ b/playbooks/roles/bifrost-ironic-install/tasks/bootstrap.yml @@ -297,13 +297,21 @@ itf_infos: "{{ internal_interface }}" dhcp_netaddr: "{{ dhcp_pool_start }}/{{ dhcp_static_mask }}" when: enable_dhcp | bool + - name: "Compute interface and DHCP network information" set_fact: itf_netaddr1: "{{ itf_infos['address'] }}/{{ itf_infos['netmask'] }}" itf_netaddr2: "{{ itf_infos['network'] }}/{{ itf_infos['netmask'] }}" - itf_broadcast: "{{ itf_infos['broadcast'] }}/{{ itf_infos['netmask'] }}" dhcp_netaddr: "{{ dhcp_netaddr | ansible.utils.ipaddr('network') }}/{{ dhcp_static_mask }}" when: enable_dhcp | bool + +- name: "Compute broadcast address for interface" + set_fact: + # NOTE: VLAN interfaces (e.g., brtest.10) may have empty broadcast field in Ansible facts. + # If broadcast is empty, compute it from the address/netmask to avoid validation failures. + itf_broadcast: "{{ (itf_infos['broadcast'] | length > 0) | ternary(itf_infos['broadcast'] + '/' + itf_infos['netmask'], itf_netaddr1 | ansible.utils.ipaddr('broadcast') + '/' + itf_infos['netmask']) }}" + when: enable_dhcp | bool + - name: "Validate interface network addresses" fail: msg: > @@ -313,6 +321,7 @@ when: - enable_dhcp | bool - itf_netaddr1 | ansible.utils.ipaddr('network') != itf_netaddr2 | ansible.utils.ipaddr('network') + - name: "Validate interface broadcast addresses" fail: msg: > @@ -322,6 +331,7 @@ when: - enable_dhcp | bool - itf_netaddr1 | ansible.utils.ipaddr('broadcast') != itf_broadcast | ansible.utils.ipaddr('broadcast') + - name: "Validate DHCP and interface addresses" debug: msg: > @@ -332,6 +342,7 @@ when: - enable_dhcp | bool - itf_netaddr2 | ansible.utils.ipaddr('network') != dhcp_netaddr | ansible.utils.ipaddr('network') + - name: "Computing new DHCP information" set_fact: dhcp_start_ip: "{{ dhcp_pool_start.split('.')[-1] }}" @@ -340,6 +351,7 @@ when: - enable_dhcp | bool - itf_netaddr2 | ansible.utils.ipaddr('network') != dhcp_netaddr | ansible.utils.ipaddr('network') + # Note(olivierbourdon38): we could do much more complex network # computation to derive exact (or way closer to exact) range for # the new network depending on netmasks and indexes. diff --git a/playbooks/roles/bifrost-ironic-install/tasks/main.yml b/playbooks/roles/bifrost-ironic-install/tasks/main.yml index 94f2a610d..f01caf469 100644 --- a/playbooks/roles/bifrost-ironic-install/tasks/main.yml +++ b/playbooks/roles/bifrost-ironic-install/tasks/main.yml @@ -34,6 +34,13 @@ include_tasks: bootstrap.yml when: not skip_bootstrap | bool +- name: "Bootstrap OVS" + include_tasks: ovs_bootstrap.yml + when: + - not skip_bootstrap | bool + - testing | bool + - test_vm_switch_type == 'ovs' + - name: "Start Ironic services" include_tasks: start.yml when: not skip_start | bool diff --git a/playbooks/roles/bifrost-ironic-install/tasks/ovs_bootstrap.yml b/playbooks/roles/bifrost-ironic-install/tasks/ovs_bootstrap.yml new file mode 100644 index 000000000..1a1aca7ad --- /dev/null +++ b/playbooks/roles/bifrost-ironic-install/tasks/ovs_bootstrap.yml @@ -0,0 +1,62 @@ +# +# 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: "Collect configured OVS VLANs from the bridge" + shell: + cmd: | + set -eo pipefail + ovs-vsctl list-ports {{ test_ovs_bridge_name }} | grep -E '\.([0-9]+)$' | sed 's/.*\.\([0-9]\+\)$/\1/' | sort -n + register: ovs_configured_vlans + when: enable_dhcp | bool + failed_when: false + changed_when: false + +- name: "Set variable with configured OVS VLAN IDs" + set_fact: + ovs_vlan_ids: "{{ ovs_configured_vlans.stdout_lines | default([]) }}" + when: enable_dhcp | bool + +- name: "Configure OVS VLAN DHCP in main dnsmasq" + template: + src: ovs-vlans-dhcp.conf.j2 + dest: "/etc/dnsmasq.d/ovs-vlans.conf" + mode: "0644" + when: enable_dhcp | bool + +- name: "Get OVS ports with Linux interfaces" + shell: + cmd: | + set -eo pipefail + for port in $(ovs-vsctl list-ports {{ test_ovs_bridge_name }}); do + if ovs-vsctl get interface $port type 2>/dev/null | grep -q "internal\|\"\""; then + if ip link show $port >/dev/null 2>&1; then + echo $port + fi + fi + done + register: ovs_linux_interfaces + when: use_firewalld | bool + +- name: "Add OVS Linux interfaces to firewall zone" + firewalld: + zone: "{{ 'libvirt' if testing | bool else firewalld_internal_zone }}" + interface: "{{ item }}" + state: enabled + permanent: yes + immediate: yes + loop: "{{ ovs_linux_interfaces.stdout_lines | default([]) }}" + when: + - use_firewalld | bool + - ovs_linux_interfaces.stdout_lines is defined + - ovs_linux_interfaces.stdout_lines | length > 0 diff --git a/playbooks/roles/bifrost-ironic-install/templates/dnsmasq.conf.j2 b/playbooks/roles/bifrost-ironic-install/templates/dnsmasq.conf.j2 index 7934abc1a..c89af1486 100644 --- a/playbooks/roles/bifrost-ironic-install/templates/dnsmasq.conf.j2 +++ b/playbooks/roles/bifrost-ironic-install/templates/dnsmasq.conf.j2 @@ -13,7 +13,9 @@ port=53 port=0 {% endif %} +{% if test_vm_switch_type == 'linux_bridge' %} listen-address={{ internal_ip }} +{% endif %} # On systems which support it, dnsmasq binds the wildcard address, # even when it is listening on only some interfaces. It then discards @@ -61,6 +63,7 @@ domain={{ domain }} # a lease time. If you have more than one network, you will need to # repeat this for each network on which you want to supply DHCP # service. +{% if test_vm_switch_type == 'linux_bridge' %} {% if testing | bool == true %} dhcp-range=192.168.122.2,192.168.122.254,12h {% elif inventory_dhcp | bool == true %} @@ -68,6 +71,7 @@ dhcp-range={{dhcp_pool_start}},static,{{dhcp_static_mask}},{{dhcp_lease_time}} {% else %} dhcp-range={{dhcp_pool_start}},{{dhcp_pool_end}},{% if dhcp_pool_mask is defined %}{{dhcp_pool_mask}},{% endif %}{{dhcp_lease_time}} {% endif %} +{% endif %} # Override the default route supplied by dnsmasq, which assumes the # router is the same machine as the one running dnsmasq. diff --git a/playbooks/roles/bifrost-ironic-install/templates/ovs-vlans-dhcp.conf.j2 b/playbooks/roles/bifrost-ironic-install/templates/ovs-vlans-dhcp.conf.j2 new file mode 100644 index 000000000..db15952ff --- /dev/null +++ b/playbooks/roles/bifrost-ironic-install/templates/ovs-vlans-dhcp.conf.j2 @@ -0,0 +1,12 @@ +# OVS VLAN DHCP configuration for main dnsmasq +# Generated by bifrost-ironic-install role during installation + +{% for vlan in ovs_vlan_ids | default([]) %} +{% set subnet_id = 100 + vlan|int %} +# DHCP configuration for VLAN {{ vlan }} (subnet 192.168.{{ subnet_id }}.0/24) +interface={{ test_ovs_bridge_name }}.{{ vlan }} +dhcp-range=tag:{{ test_ovs_bridge_name }}.{{ vlan }},192.168.{{ subnet_id }}.10,192.168.{{ subnet_id }}.100,255.255.255.0,12h +dhcp-option=tag:{{ test_ovs_bridge_name }}.{{ vlan }},3,192.168.{{ subnet_id }}.1 +dhcp-option=tag:{{ test_ovs_bridge_name }}.{{ vlan }},6,192.168.{{ subnet_id }}.1 + +{% endfor %} diff --git a/releasenotes/notes/ovs-virtual-switch-support-36b3693a1076e2f6.yaml b/releasenotes/notes/ovs-virtual-switch-support-36b3693a1076e2f6.yaml new file mode 100644 index 000000000..b7b9ad660 --- /dev/null +++ b/releasenotes/notes/ovs-virtual-switch-support-36b3693a1076e2f6.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Adds support for using Open vSwitch (OVS) as a virtual switch for testing + environments. Setting ``test_vm_switch_type`` to ``ovs`` creates 3 separate + VLANs for comprehensive network testing: one dedicated as an inspection + network, another for the final tenant network, and a third for all other + network types (cleaning, rescuing, servicing, etc.). This enhancement + enables testing of the standalone networking feature with proper VLAN + configuration support through the networking generic switch driver.