diff --git a/.gitignore b/.gitignore index 894a44c..890ac8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# All dot-files +.* +# Ansible retry files +*.retry + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/ansible/group_vars/all b/ansible/group_vars/all new file mode 100644 index 0000000..dd15b0c --- /dev/null +++ b/ansible/group_vars/all @@ -0,0 +1,41 @@ +--- +# Map physical network names to their source device. This can be either an +# existing interface or an existing bridge. +physnet_mappings: {} + +virtualisation_provider: libvirt + +system_requirements: + - python-pip + - python-virtualenv + +# Path to virtualenv used to install Python requirements. If a virtualenv does +# not exist at this location, one will be created. +virtualenv_path: ~/tenks-venv + +python_requirements: + - virtualbmc + +# Naming scheme for bridges created by tenks for physical networks is +# {{ bridge_prefix + i }}, where `i` is the index of the physical network in +# physnet_mappings (sorted alphabetically by key). +bridge_prefix: brtenks +# Naming scheme for veth pairs connected to the Tenks OVS bridge. +veth_prefix: p- +# Used for the port on the Tenks OVS bridge. +veth_tenks_suffix: "-ovs" +# Used for the port on the existing bridge. +veth_source_suffix: "-phy" + +libvirt_pool_name: tenks +libvirt_pool_path: /var/lib/libvirt/tenks_pool/ +libvirt_pool_type: dir +# Capacity is irrelevant for directory-based pools. +libvirt_pool_capacity: +libvirt_pool_mode: 755 +libvirt_pool_owner: "{ remote_user }}" +libvirt_pool_group: "{ remote_user }}" + +# By default, allow QEMU without hardware virtualisation since this is a +# development tool. +libvirt_require_vt: false diff --git a/ansible/group_vars/libvirt b/ansible/group_vars/libvirt new file mode 100644 index 0000000..694a712 --- /dev/null +++ b/ansible/group_vars/libvirt @@ -0,0 +1,13 @@ +--- +libvirt_pool_name: tenks +libvirt_pool_path: /var/lib/libvirt/tenks_pool/ +libvirt_pool_type: dir +# Capacity is irrelevant for directory-based pools. +libvirt_pool_capacity: +libvirt_pool_mode: 755 +libvirt_pool_owner: "{{ ansible_user_id }}" +libvirt_pool_group: "{{ ansible_user_id }}" + +# By default, allow QEMU without hardware virtualisation since this is a +# development tool. +libvirt_require_vt: false diff --git a/ansible/host_setup.yml b/ansible/host_setup.yml new file mode 100644 index 0000000..e7520c5 --- /dev/null +++ b/ansible/host_setup.yml @@ -0,0 +1,44 @@ +--- +- name: Ensure general system requirements are installed + yum: + name: "{{ system_requirements }}" + become: true + +- name: Check if ovs-vsctl command is present + shell: ovs-vsctl --version + register: ovs_vsctl_check + failed_when: false + changed_when: false + +- block: + - name: Ensure Open vSwitch package is installed + yum: + name: openvswitch + become: true + + - name: Ensure Open vSwitch is started and enabled + service: + name: openvswitch + state: running + enabled: true + become: true + # Return code 127 means the command does not exist. Do this check to avoid + # installing Open vSwitch system-wide if the command already exists as a link + # to a containerised version of OVS. + when: ovs_vsctl_check.rc == 127 + +- name: Configure physical network + include_tasks: physical_network.yml + vars: + network_name: "{{ item.0 }}" + tenks_bridge: "{{ bridge_prefix ~ idx }}" + source_interface: "{{ item.1 }}" + # Sort to ensure we always enumerate in the same order. + loop: "{{ physnet_mappings | dictsort }}" + loop_control: + index_var: idx + +- name: Ensure Python requirements are installed + pip: + name: "{{ python_requirements }}" + virtualenv: "{{ virtualenv_path }}" diff --git a/ansible/main.yml b/ansible/main.yml new file mode 100644 index 0000000..66ce81d --- /dev/null +++ b/ansible/main.yml @@ -0,0 +1,20 @@ +--- +- hosts: all + tasks: + - include_tasks: host_setup.yml + +- hosts: libvirt + tasks: + - name: Configure host as a Libvirt/QEMU/KVM hypervisor + include_role: + name: stackhpc.libvirt-host + vars: + libvirt_host_pools: + - name: "{{ libvirt_pool_name }}" + type: "{{ libvirt_pool_type }}" + capacity: "{{ libvirt_pool_capacity }}" + path: "{{ libvirt_pool_path }}" + mode: "{{ libvirt_pool_mode }}" + owner: "{{ libvirt_pool_owner }}" + group: "{{ libvirt_pool_group }}" + libvirt_host_require_vt: "{{ libvirt_require_vt }}" diff --git a/ansible/physical_network.yml b/ansible/physical_network.yml new file mode 100644 index 0000000..aaa94c1 --- /dev/null +++ b/ansible/physical_network.yml @@ -0,0 +1,103 @@ +--- +- name: Fail if source interface does not exist + fail: + msg: > + The interface {{ source_interface }} specified for the physical network + {{ network_name }} does not exist. + when: source_interface not in ansible_interfaces + +### Firstly, some fact gathering. +# Start off by assuming the source interface is direct, unless proven +# otherwise. +- set_fact: + source_type: 'direct' + +- name: Get source interface details + command: ip -details link show {{ source_interface }} + register: if_details + changed_when: false + +- name: Register source interface as a Linux bridge + set_fact: + source_type: linux_bridge + when: if_details.stdout_lines[-1].split()[0] == 'bridge' + +- block: + - name: Get list of OVS bridges + command: ovs-vsctl list-br + register: ovs_bridges + changed_when: false + + - name: Register source interface as an Open vSwitch bridge + set_fact: + source_type: ovs_bridge + when: source_interface in ovs_bridges.stdout_lines + + when: if_details.stdout_lines[-1].split()[0] == 'openvswitch' + + +### Actual configuration starts here. +- name: Ensure Open vSwitch bridge exists + openvswitch_bridge: + bridge: "{{ tenks_bridge }}" + +- name: Connect to existing Linux bridge + when: source_type == 'linux_bridge' + block: + - name: Create veth pair + command: > + ip link add dev {{ veth_prefix + tenks_bridge + veth_tenks_suffix }} + type veth + peer name {{ veth_prefix + tenks_bridge + veth_source_suffix }} + register: res + changed_when: res.rc == 0 + # Return code 2 means the veth pair already exists + failed_when: res.rc not in [0, 2] + become: true + + - name: Plug veth into Tenks bridge + openvswitch_port: + bridge: "{{ tenks_bridge }}" + port: "{{ veth_prefix + tenks_bridge + veth_tenks_suffix }}" + + - name: Plug veth into source interface + command: > + brctl addif {{ source_interface }} + {{ veth_prefix + tenks_bridge + veth_source_suffix }} + register: res + failed_when: + - res.rc != 0 + - "'already a member of a bridge' not in res.stderr" + changed_when: "'already a member of a bridge' not in res.stderr" + become: true + +- name: Connect to existing Open vSwitch bridge + when: source_type == 'ovs_bridge' + block: + - name: Create patch port on Tenks bridge + openvswitch_port: + bridge: "{{ tenks_bridge }}" + port: "{{ veth_prefix + tenks_bridge + veth_tenks_suffix }}" + # Despite the module documentation, `set` will happily take multiple + # properties. + set: > + Interface {{ veth_prefix + tenks_bridge + veth_tenks_suffix }} + type=patch + options:peer={{ veth_prefix + tenks_bridge + veth_source_suffix }} + + - name: Create patch port on source bridge + openvswitch_port: + bridge: "{{ source_interface }}" + port: "{{ veth_prefix + tenks_bridge + veth_source_suffix }}" + set: > + Interface {{ veth_prefix + tenks_bridge + veth_source_suffix }} + type=patch + options:peer={{ veth_prefix + tenks_bridge + veth_tenks_suffix }} + + when: if_details.stdout_lines[-1].split()[0] == 'openvswitch' + +- name: Plug source interface into Tenks bridge + when: source_type == 'direct' + openvswitch_port: + bridge: "{{ tenks_bridge }}" + port: "{{ source_interface }}" diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..1c954d3 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,3 @@ +--- +src: stackhpc.libvirt-host +src: stackhpc.libvirt-vm