diff --git a/examples/playbooks/libvirt/create-network.yml b/examples/playbooks/libvirt/create-network.yml new file mode 100644 index 0000000..9615a88 --- /dev/null +++ b/examples/playbooks/libvirt/create-network.yml @@ -0,0 +1,36 @@ +- hosts: primary + roles: + - role: libvirt-network + become: true + vars: + network_action: create + libvirt_network: + name: provision-network + spec: + forward: + mode: nat + nat: + port: + - start: 1024 + end: 65535 + bridge: + name: "prov-net-br" + stp: 'on' + delay: '0' + ip: + address: "172.22.0.1" + netmask: "255.255.255.0" + - role: libvirt-network + become: true + vars: + network_action: rebuild + libvirt_network: + name: oob-net + spec: + bridge: + name: oob-net + stp: 'on' + delay: '0' + ip: + address: "10.23.22.1" + netmask: "255.255.255.0" diff --git a/examples/playbooks/libvirt/create-pool.yml b/examples/playbooks/libvirt/create-pool.yml new file mode 100644 index 0000000..6e560a5 --- /dev/null +++ b/examples/playbooks/libvirt/create-pool.yml @@ -0,0 +1,8 @@ +- hosts: primary + roles: + - role: libvirt-pool + become: true + vars: + libvirt_pool: + path: /var/lib/libvirt/airship + name: airship \ No newline at end of file diff --git a/examples/playbooks/libvirt/create-vm.yml b/examples/playbooks/libvirt/create-vm.yml new file mode 100644 index 0000000..6ac7498 --- /dev/null +++ b/examples/playbooks/libvirt/create-vm.yml @@ -0,0 +1,32 @@ +- hosts: primary + roles: + - role: libvirt-domain + become: true + vars: + libvirt_domain: + state: running + name: 'vm1' + memory_mb: 512 + vcpus: 1 + volumes: + - name: 'volume-1' + device: 'disk' + format: 'qcow2' + pool: 'airship' + interfaces: + - network: 'provision-network' + - role: libvirt-domain + become: true + vars: + libvirt_domain: + state: running + name: 'vm2' + memory_mb: 512 + vcpus: 1 + volumes: + - name: 'volume-2' + device: 'disk' + format: 'qcow2' + pool: 'airship' + interfaces: + - network: 'provision-network' diff --git a/examples/playbooks/libvirt/create-volume.yml b/examples/playbooks/libvirt/create-volume.yml new file mode 100644 index 0000000..84eea36 --- /dev/null +++ b/examples/playbooks/libvirt/create-volume.yml @@ -0,0 +1,22 @@ +- hosts: primary + tasks: + - name: Create defined volumes + include_role: + name: libvirt-volume + with_items: + - name: volume-1 + image: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 + size: 10G + pool: airship + action: create + - name: volume-2 + image: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 + size: 10G + pool: airship + action: create + vars: + libvirt_volume: "{{ vol }}" + volume_action: "{{ vol.action }}" + ansible_become: true + loop_control: + loop_var: vol \ No newline at end of file diff --git a/examples/playbooks/libvirt/install.yaml b/examples/playbooks/libvirt/install.yaml new file mode 100644 index 0000000..7d72189 --- /dev/null +++ b/examples/playbooks/libvirt/install.yaml @@ -0,0 +1,4 @@ +--- +- hosts: primary + roles: + - role: libvirt-install diff --git a/examples/playbooks/redfish/install-sushy.yaml b/examples/playbooks/redfish/install-sushy.yaml new file mode 100644 index 0000000..591c030 --- /dev/null +++ b/examples/playbooks/redfish/install-sushy.yaml @@ -0,0 +1,3 @@ +- hosts: primary + roles: + - role: redfish-emulator \ No newline at end of file diff --git a/roles/libvirt-domain/defaults/main.yml b/roles/libvirt-domain/defaults/main.yml new file mode 100644 index 0000000..8141f1e --- /dev/null +++ b/roles/libvirt-domain/defaults/main.yml @@ -0,0 +1,173 @@ +--- + +# The default directory in which to store VM console logs, if a VM-specific log +# file path is not given. +libvirt_vm_default_console_log_dir: "/var/log/libvirt-consoles/" + +# The default location for libvirt images +libvirt_volume_default_images_path: '/var/lib/libvirt/images' + +# Default type for Libvirt volumes +libvirt_volume_default_type: volume + +# The default format for Libvirt volumes. +libvirt_volume_default_format: qcow2 + +# The default device for Libvirt volumes. +libvirt_volume_default_device: disk + +# CPU architecture. +libvirt_vm_arch: x86_64 + +# Virtualisation engine. If not set, the role will attempt to auto-detect the +# optimal engine to use. +libvirt_vm_engine: + +# Path to emulator binary. If not set, the role will attempt to auto-detect the +# correct emulator to use. +libvirt_vm_emulator: + +# Default value for clock syncing. The default (false) uses +# to configure the instances clock synchronisation. Change to a timezone to make +# configuration use +libvirt_vm_clock_offset: False + +# A list of specifications of VMs to be created. +# For backwards compatibility, libvirt_vms defaults to a singleton list using +# the values of the deprecated variables below. +# See README.md or tasks/main.yml for these attributes' defaults. +libvirt_domain: + # State of the VM. May be 'present' or 'absent'. + state: "{{ libvirt_vm_state }}" + + # Name of the VM. + name: "{{ libvirt_vm_name }}" + + # Memory in MB. + memory_mb: "{{ libvirt_vm_memory_mb }}" + + # Number of vCPUs. + vcpus: "{{ libvirt_vm_vcpus }}" + + # Virtual machine type. + machine: "{{ libvirt_vm_machine }}" + + # Virtual machine CPU mode. + cpu_mode: "{{ libvirt_vm_cpu_mode | default(libvirt_cpu_mode_default, true) }}" + + # List of volumes. + volumes: "{{ libvirt_vm_volumes }}" + + # What time should the clock be synced to on boot (utc/localtime/timezone/variable) + clock_offset: "localtime" + + # List of network interfaces. + interfaces: "{{ libvirt_vm_interfaces }}" + + # Path to console log file. + console_log_path: "{{ libvirt_vm_console_log_path }}" + + # XML template file to source domain definition + xml_file: vm.xml.j2 + +# Variables to add to the enviroment that is used to execute virsh commands +libvirt_vm_virsh_default_env: "{{ { 'LIBVIRT_DEFAULT_URI': libvirt_vm_uri } if libvirt_vm_uri else {} }}" + +# Override for the libvirt connection uri. Leave unset to use the default. +libvirt_vm_uri: "" + +# Default CPU mode if libvirt_vm_cpu_mode or vm.cpu_mode is undefined +libvirt_cpu_mode_default: "{{ 'host-passthrough' if libvirt_vm_engine == 'kvm' else 'host-model' }}" + +libvirt_domain_template_default: | + + {{ libvirt_domain.name }} + {{ libvirt_domain.memory_mb | int * 1024 }} + {{ libvirt_domain.vcpus }} + {% if libvirt_domain.clock_offset |default( libvirt_vm_clock_offset ) %} + + {% else %} + + {% endif %} + destroy + restart + destroy + + hvm + + + + + + + + + + + + {% if cpu_mode %} + + + + {% endif %} + + {{ libvirt_vm_emulator }} + {% for volume in volumes %} + + + {% if volume.type | default(libvirt_volume_default_type) == 'file' %} + + {% else %} + + {% endif %} + {% if volume.target is undefined %} + + {% else %} + + {% endif %} + + {% endfor %} + {% for interface in interfaces %} + {% if interface.type is defined and interface.type == 'direct' %} + + + {% elif interface.type is defined and interface.type == 'bridge' %} + + + {% elif interface.type is not defined or interface.type == 'network' %} + + + {% endif %} + {% if interface.mac is defined %} + + {% endif %} + {# if the network configuration is invalid this can still appear in the xml #} + {# (say you enter 'bond' instead of 'bridge' in your variables) #} + + + {% endfor %} + {% if console_log_enabled | bool %} + + + + + + + + + {% else %} + + + + + + + {% endif %} + {% if enable_vnc |bool %} + + + + {% endif %} + /dev/urandom + + diff --git a/roles/libvirt-domain/tasks/autodetect.yml b/roles/libvirt-domain/tasks/autodetect.yml new file mode 100644 index 0000000..e7cc39e --- /dev/null +++ b/roles/libvirt-domain/tasks/autodetect.yml @@ -0,0 +1,65 @@ +--- +- name: Detect the virtualisation engine + block: + - name: Load the kvm kernel module + modprobe: + name: kvm + become: true + failed_when: false + + - name: Check for the KVM device + stat: + path: /dev/kvm + register: stat_kvm + + - name: Set a fact containing the virtualisation engine + set_fact: + libvirt_vm_engine: >- + {%- if ansible_architecture != libvirt_vm_arch -%} + {# Virtualisation instructions are generally available only for the host + architecture. Ideally we would test for virtualisation instructions, eg. vt-d + as it is possible that another architecture could support these even + if the emulated cpu architecture is not the same. #} + qemu + {%- elif stat_kvm.stat.exists -%} + kvm + {%- else -%} + qemu + {%- endif -%} + when: libvirt_vm_engine is none or libvirt_vm_engine | length == 0 + +- name: Detect the virtualisation emulator + block: + - block: + - name: Detect the KVM emulator binary path + stat: + path: "{{ item }}" + register: kvm_emulator_result + with_items: + - /usr/bin/kvm + - /usr/bin/qemu-kvm + - /usr/libexec/qemu-kvm + + - name: Set a fact containing the KVM emulator binary path + set_fact: + libvirt_vm_emulator: "{{ item.item }}" + with_items: "{{ kvm_emulator_result.results }}" + when: item.stat.exists + when: libvirt_vm_engine == 'kvm' + + - block: + - name: Detect the QEMU emulator binary path + shell: which qemu-system-{{ libvirt_vm_arch }} + register: qemu_emulator_result + changed_when: false + + - name: Set a fact containing the QEMU emulator binary path + set_fact: + libvirt_vm_emulator: "{{ qemu_emulator_result.stdout }}" + when: libvirt_vm_engine == 'qemu' + + - name: Fail if unable to detect the emulator + fail: + msg: Unable to detect emulator for engine {{ libvirt_vm_engine }}. + when: libvirt_vm_emulator is none + when: libvirt_vm_emulator is none or libvirt_vm_emulator | length == 0 \ No newline at end of file diff --git a/roles/libvirt-domain/tasks/check-interface.yml b/roles/libvirt-domain/tasks/check-interface.yml new file mode 100644 index 0000000..cdf009a --- /dev/null +++ b/roles/libvirt-domain/tasks/check-interface.yml @@ -0,0 +1,21 @@ +--- +- name: Check network interface has a network name + fail: + msg: > + The interface definition {{ interface }} has type 'network', but does not have + a network name defined. + when: + - interface.type is not defined or + interface.type == 'network' + - interface.network is not defined + +- name: Check direct interface has an interface device name + fail: + msg: > + The interface definition {{ interface }} has type 'direct', but does not have + a host source device defined. + when: + - interface.type is defined + - interface.type == 'direct' + - interface.source is not defined or + interface.source.dev is not defined \ No newline at end of file diff --git a/roles/libvirt-domain/tasks/domain.yml b/roles/libvirt-domain/tasks/domain.yml new file mode 100644 index 0000000..4945a09 --- /dev/null +++ b/roles/libvirt-domain/tasks/domain.yml @@ -0,0 +1,28 @@ +--- +- name: Ensure the VM console log directory exists + file: + path: "{{ libvirt_domain.console_log_path | dirname }}" + state: directory + owner: "{{ libvirt_domain.libvirt_vm_log_owner }}" + group: "{{ libvirt_domain.libvirt_vm_log_owner }}" + recurse: true + mode: 0770 + when: "libvirt_domain.console_log_enabled | default('false') | bool" + +- name: Validate VM interfaces + include_tasks: check-interface.yml + vars: + interface: "{{ item }}" + with_items: "{{ libvirt_domain.interfaces }}" + +- name: Ensure the VM is defined + virt: + name: "{{ libvirt_domain.name }}" + command: define + xml: "{{ libvirt_domain.xml | default(libvirt_domain_template_default) }}" + +- name: Ensure the VM is started at boot + virt: + name: "{{ libvirt_domain.name }}" + autostart: "{{ libvirt_domain.autostart | default(false) }}" + state: "{{ libvirt_domain.state | default('running') }}" diff --git a/roles/libvirt-domain/tasks/main.yaml b/roles/libvirt-domain/tasks/main.yaml new file mode 100644 index 0000000..96bc4c6 --- /dev/null +++ b/roles/libvirt-domain/tasks/main.yaml @@ -0,0 +1,16 @@ +- include_tasks: autodetect.yml + +- include_tasks: domain.yml + vars: + console_log_enabled: "{{ libvirt_domain.console_log_enabled | default(false) }}" + console_log_path: >- + {{ libvirt_domain.console_log_path | + default(libvirt_vm_default_console_log_dir + '/' + libvirt_domain.name + '-console.log', true) }} + machine_default: "{{ none if libvirt_vm_engine == 'kvm' else 'pc-1.0' }}" + machine: "{{ libvirt_domain.machine | default(machine_default, true) }}" + cpu_mode: "{{ libvirt_domain.cpu_mode | default(libvirt_cpu_mode_default) }}" + volumes: "{{ libvirt_domain.volumes | default([], true) }}" + interfaces: "{{ libvirt_domain.interfaces | default([], true) }}" + start: "{{ libvirt_domain.start | default(true) }}" + autostart: "{{ libvirt_domain.autostart | default(true) }}" + enable_vnc: "{{ libvirt_domain.enable_vnc | default(false) }}" diff --git a/roles/libvirt-domain/templates/domain.xml.j2 b/roles/libvirt-domain/templates/domain.xml.j2 new file mode 100644 index 0000000..0dca8cf --- /dev/null +++ b/roles/libvirt-domain/templates/domain.xml.j2 @@ -0,0 +1,91 @@ + + {{ libvirt_domain.name }} + {{ libvirt_domain.memory_mb | int * 1024 }} + {{ libvirt_domain.vcpus }} + {% if libvirt_domain.clock_offset |default( libvirt_vm_clock_offset ) %} + + {% else %} + + {% endif %} + destroy + restart + destroy + + hvm + + + + + + + + + + + + {% if cpu_mode %} + + + + {% endif %} + + {{ libvirt_vm_emulator }} +{% for volume in volumes %} + + + {% if volume.type | default(libvirt_volume_default_type) == 'file' %} + + {% else %} + + {% endif %} + {% if volume.target is undefined %} + + {% else %} + + {% endif %} + +{% endfor %} +{% for interface in interfaces %} +{% if interface.type is defined and interface.type == 'direct' %} + + +{% elif interface.type is defined and interface.type == 'bridge' %} + + +{% elif interface.type is not defined or interface.type == 'network' %} + + +{% endif %} + {% if interface.mac is defined %} + + {% endif %} + {# if the network configuration is invalid this can still appear in the xml #} + {# (say you enter 'bond' instead of 'bridge' in your variables) #} + + +{% endfor %} +{% if console_log_enabled | bool %} + + + + + + + + +{% else %} + + + + + + +{% endif %} +{% if enable_vnc |bool %} + + + +{% endif %} + /dev/urandom + + diff --git a/roles/libvirt-domain/tests/main.yml b/roles/libvirt-domain/tests/main.yml new file mode 100644 index 0000000..c0cb9d7 --- /dev/null +++ b/roles/libvirt-domain/tests/main.yml @@ -0,0 +1,39 @@ +- name: Include test variables. + include_vars: + file: vars.yml +- name: install libvirt + include_role: + name: libvirt-install +- name: create pool + include_role: + name: libvirt-pool + vars: + ansible_become: true +- name: Create defined volumes + include_role: + name: libvirt-volume + with_items: "{{ libvirt_volumes }}" + vars: + libvirt_volume: "{{ vol }}" + volume_action: "{{ vol.action }}" + ansible_become: true + loop_control: + loop_var: vol +- name: create libvirt domains + include_role: + name: libvirt-domain + vars: + ansible_become: true +- name: save information about domain + virt: + command: info + name: "{{ libvirt_domain.name }}" + register: domain_info + become: true +- name: debug domain-info + debug: + var: domain_info +- name: make sure that vm is in correct state + assert: + that: + - domain_info[libvirt_domain.name].state == libvirt_domain.state diff --git a/roles/libvirt-domain/tests/vars.yml b/roles/libvirt-domain/tests/vars.yml new file mode 100644 index 0000000..11b9852 --- /dev/null +++ b/roles/libvirt-domain/tests/vars.yml @@ -0,0 +1,44 @@ +libvirt_pool: + path: /var/lib/libvirt/airship + name: airship + +libvirt_volumes: + - name: volume-1 + image: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create + - name: volume-2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create + +libvirt_domain: + state: running + name: 'vm1' + memory_mb: 2048 + vcpus: 1 + volumes: + - name: 'volume-1' + device: 'disk' + format: 'qcow2' + pool: 'airship' + interfaces: + - network: 'provision-network' + +libvirt_network: + name: provision-network + spec: + forward: + mode: nat + nat: + port: + - start: 1024 + end: 65535 + bridge: + name: "prov-net-br" + stp: 'on' + delay: '0' + ip: + address: "172.22.0.1" + netmask: "255.255.255.0" \ No newline at end of file diff --git a/roles/libvirt-install/tasks/main.yaml b/roles/libvirt-install/tasks/main.yaml new file mode 100644 index 0000000..67c09e9 --- /dev/null +++ b/roles/libvirt-install/tasks/main.yaml @@ -0,0 +1,38 @@ +--- +- block: + - name: Ensuring Libvirt, Qemu and support packages are present + become: true + when: ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux' + yum: + name: + - libguestfs-tools + - libvirt + - libvirt-devel + - libvirt-daemon-kvm + - qemu-kvm + - virt-install + state: present + - name: Ensuring Libvirt, Qemu and support packages are present + become: true + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' + apt: + name: + - qemu + - libvirt-bin + - libguestfs-tools + - qemu-kvm + - virtinst + - python-lxml + - python3-lxml + - python3-libvirt + - python-libvirt + - dnsmasq + - ebtables + state: present + - name: Start libvirtd + service: + name: libvirtd + state: started + enabled: true + become: true + diff --git a/roles/libvirt-network/defaults/main.yml b/roles/libvirt-network/defaults/main.yml new file mode 100644 index 0000000..31c73d0 --- /dev/null +++ b/roles/libvirt-network/defaults/main.yml @@ -0,0 +1,153 @@ +# libvirt_network: +# name: provision-network +# spec: +# forward: +# mode: nat +# nat: +# port: +# - start: 1024 +# end: 65535 +# bridge: +# name: "prov-net-br" +# stp: 'on' +# delay: '0' +# ip: +# address: "172.22.0.1" +# netmask: "255.255.255.0" +# libvirt_network: +# name: "{{ bm_net_name }}" +# persistent: true +# autostart: true +# spec: +# forward: +# mode: nat +# nat: +# port: +# - start: 1024 +# end: 65535 +# bridge: +# name: "{{ bm_net_name }}" +# stp: 'on' +# delay: '0' +# domain: +# name: 'tests.baremetal.net' +# localOnly: 'yes' +# dns: +# - forwarder: +# domain: 'apps.tests.baremetal.net' +# addr: '127.0.0.1' +# - forwarder: +# domain: 'services.tests.baremetal.net' +# addr: '127.0.0.1' +# ip: "{{ bm_net_0_ip_cfg }}" + +libvirt_network_template_default: | + + {{ net_yaml.name }} + {% if net_yaml.forward is defined %} + {% if net_yaml.forward.mode is defined %} + + {% else %} + + {% endif %} + + {% if net_yaml.forward.nat is defined %} + + {% if net_yaml.forward.nat.port is defined %} + {% for port in net_yaml.forward.nat.port %} + + {% endfor %} + {% endif %} + + {% endif %} + + {% endif %} + {% if net_yaml.bridge is defined %} + + {% endif %} + {% if net_yaml.mac is defined %} + + {% endif %} + {% if net_yaml.domain is defined %} + + {% endif %} + {% if net_yaml.dns is defined %} + + {% if net_yaml.dns | list %} + {% for dns_item in net_yaml.dns %} + {% if dns_item.forwarder is defined %} + + {% endif %} + {% endfor %} + {% endif %} + + {% endif %} + {% if net_yaml.ip is defined %} + + {% if net_yaml.ip.dhcp is defined %} + + {% for dhcp_item in net_yaml.ip.dhcp %} + {% if dhcp_item.range is defined %} + + {% endif %} + {% if dhcp_item.host is defined %} + + {% endif %} + {% endfor %} + + {% endif %} + + {% endif %} + diff --git a/roles/libvirt-network/tasks/add_dhcp_hosts.yml b/roles/libvirt-network/tasks/add_dhcp_hosts.yml new file mode 100644 index 0000000..2acacd0 --- /dev/null +++ b/roles/libvirt-network/tasks/add_dhcp_hosts.yml @@ -0,0 +1,32 @@ +# Description: +# Add given hosts to existing libvirt network +# +# Inputs: +# network_action: "add_dhcp_hosts" +# network_args: +# name: +# hosts: +# - name: +# mac: +# ip: +# - name: +# mac: +# ip: + +- name: Validate input + assert: + that: + - "network_args is defined" + - "network_args.name is defined" + - "network_args.hosts is defined" + - "network_args.hosts | list" + +- name: add dhcp hosts to network + shell: >- + virsh net-update {{ network_args.name }} \ + add --section ip-dhcp-host \ + --xml "" \ + --config --live + loop: "{{ network_args.hosts }}" + loop_control: + loop_var: single_dhcp_host diff --git a/roles/libvirt-network/tasks/create.yml b/roles/libvirt-network/tasks/create.yml new file mode 100644 index 0000000..ea24f87 --- /dev/null +++ b/roles/libvirt-network/tasks/create.yml @@ -0,0 +1,73 @@ +# Description: +# Creates a libvirt network. libvirt_network are +# exactly converted to XML from YAML so there +# is no validation whether the arguments are +# correct or not. Caller must ensure that yaml +# is formulated correctly. +# +# Inputs: +# network_action: "create" +# libvirt_network: +# name: +# persistent: +# autostart: +# recreate: +# spec: +# forward: +# mode: +# nat: +# port: +# - start: +# end: +# bridge: +# name: +# stp: +# delay: +# domain: +# name: +# localOnly: +# dns: +# forwarder: +# domain: +# addr: +# mac: +# address: +# ip: +# address: +# netmask: +# dhcp: +# - range: +# start: +# end: + +- name: Validate input + assert: + that: + - "libvirt_network is defined" + - "libvirt_network.name is defined" + - "libvirt_network.spec is defined" + +- name: Create yaml for template + set_fact: + net_yaml: >- + {{ + libvirt_network.spec + | combine({'name': libvirt_network.name}, recursive=True) + }} + +- name: "Define network" + virt_net: + command: define +# If libvirt_network.xml is defined, spec will be ignored. + xml: "{{ libvirt_network.xml | default(libvirt_network_template_default) }}" + name: "{{ libvirt_network.name }}" + +- name: "Start network" + virt_net: + state: active + name: "{{ libvirt_network.name }}" + +- name: "Autostart network" + virt_net: + name: "{{ libvirt_network.name }}" + autostart: "{{ libvirt_network.autostart |default(true) }}" diff --git a/roles/libvirt-network/tasks/main.yaml b/roles/libvirt-network/tasks/main.yaml new file mode 100644 index 0000000..175f17d --- /dev/null +++ b/roles/libvirt-network/tasks/main.yaml @@ -0,0 +1 @@ +- include_tasks: "{{ network_action }}.yml" diff --git a/roles/libvirt-network/tasks/rebuild.yml b/roles/libvirt-network/tasks/rebuild.yml new file mode 100644 index 0000000..ab08c5d --- /dev/null +++ b/roles/libvirt-network/tasks/rebuild.yml @@ -0,0 +1,17 @@ +- name: "Remove network" + virt_net: + state: absent + name: "{{ libvirt_network.name }}" + +- name: Create yaml for template + set_fact: + net_yaml: >- + {{ + libvirt_network.spec + | combine({'name': libvirt_network.name}, recursive=True) + }} + +- name: "create network" + include_tasks: "{{ network_action }}.yml" + vars: + network_action: create diff --git a/roles/libvirt-network/templates/network.xml.j2 b/roles/libvirt-network/templates/network.xml.j2 new file mode 100644 index 0000000..00e78db --- /dev/null +++ b/roles/libvirt-network/templates/network.xml.j2 @@ -0,0 +1,110 @@ + + {{ net_yaml.name }} + {% if net_yaml.forward is defined %} + {% if net_yaml.forward.mode is defined %} + + {% else %} + + {% endif %} + + {% if net_yaml.forward.nat is defined %} + + {% if net_yaml.forward.nat.port is defined %} + {% for port in net_yaml.forward.nat.port %} + + {% endfor %} + {% endif %} + + {% endif %} + + {% endif %} + {% if net_yaml.bridge is defined %} + + {% endif %} + {% if net_yaml.mac is defined %} + + {% endif %} + {% if net_yaml.domain is defined %} + + {% endif %} + {% if net_yaml.dns is defined %} + + {% if net_yaml.dns | list %} + {% for dns_item in net_yaml.dns %} + {% if dns_item.forwarder is defined %} + + {% endif %} + {% endfor %} + {% endif %} + + {% endif %} + {% if net_yaml.ip is defined %} + + {% if net_yaml.ip.dhcp is defined %} + + {% for dhcp_item in net_yaml.ip.dhcp %} + {% if dhcp_item.range is defined %} + + {% endif %} + {% if dhcp_item.host is defined %} + + {% endif %} + {% endfor %} + + {% endif %} + + {% endif %} + + diff --git a/roles/libvirt-network/tests/main.yml b/roles/libvirt-network/tests/main.yml new file mode 100644 index 0000000..1e977a6 --- /dev/null +++ b/roles/libvirt-network/tests/main.yml @@ -0,0 +1,139 @@ +- name: Include test variables. + include_vars: + file: vars.yml +- name: install libvirt + include_role: + name: libvirt-install +- name: create networks + include_role: + name: libvirt-network + with_items: "{{ libvirt_networks }}" + loop_control: + loop_var: libvirt_network + vars: + ansible_become: true + network_action: "{{ libvirt_network.network_action }}" +- name: install required packages + apt: + name: + - bridge-utils + state: present + become: true +- name: gather network info + virt_net: + command: info + register: libvirt_networks_info + become: true + +- name: debug network list + debug: + var: libvirt_networks_info + +- name: check if network is present + assert: + that: + - "'oob-net' in libvirt_networks_info.networks" + - "'provision-network' in libvirt_networks_info.networks" + +## this is needed because dashes '-', are not proccessed in expected way to ansible +- name: Assign networks to separate variables + set_fact: + oob_net: "{{ libvirt_networks_info.networks['oob-net'] }}" + provision_network: "{{ libvirt_networks_info.networks['provision-network'] }}" + +- name: Verify oob network is in correct state + assert: + that: + - "oob_net.autostart == 'no'" + - "oob_net.bridge == 'oob-net'" + - "oob_net.state == 'active'" + +- name: register ip address of the oob-net interface + command: ip -4 a show dev oob-net + register: oob_net_device + changed_when: false + +- name: debug oob-net interface + debug: + var: oob_net_device.stdout + +- name: verify oob-net bridge has correct address + assert: + that: "'10.23.22.1/24' in oob_net_device.stdout" + +- name: Verify provision-network is in correct state + assert: + that: + - "provision_network.autostart == 'yes'" + - "provision_network.bridge == 'prov-net-br'" + - "provision_network.state == 'active'" + - "provision_network.forward_mode == 'nat'" + +- name: register ip address of the oob-net interface + command: ip -4 a show dev prov-net-br + register: prov_net_br_device + changed_when: false + +- name: debug prov-net-br interface + debug: + var: prov_net_br_device.stdout + +- name: verify provision-network bridge has correct address + assert: + that: "'172.22.0.1/24' in prov_net_br_device.stdout" + +- name: Create virtual ethernet interface + command: ip link add name air02 type veth peer name air01 + become: true + changed_when: + - "create_veth_command.rc != 2" + - "'RTNETLINK answers: File exists' not in (create_veth_command.stderr | default(''))" + register: create_veth_command + failed_when: + - "create_veth_command.rc != 0" + - "'RTNETLINK answers: File exists' not in (create_veth_command.stderr | default(''))" +- name: set interface up + become: true + command: ip link set up dev air02 + # This makes task never report to be changed, it is a workaround + # because if device is already up there is no command output or different RC + changed_when: false + +- name: set interface up + become: true + command: ip link set up dev air01 + # This makes task never report to be changed, it is a workaround + # because if device is already up there is no command output or different RC + changed_when: false + +- name: set interface already in bridge variable + set_fact: + already_in_bridge: device air02 is already a member of a bridge; can't enslave it to bridge oob-net. + +- name: Add interface to libvirt managed linux bridge with dhcp + become: true + command: brctl addif oob-net air02 + changed_when: + - add_if_command.rc != 1 + - already_in_bridge not in (dd_if_command.stderr | default('')) + failed_when: + - add_if_command.rc != 0 + - already_in_bridge not in add_if_command.stderr | default('') + register: add_if_command + +- name: send dhcp request over the interface + become: true + command: timeout 20s dhclient air01 + changed_when: false + +- name: register ip address of the air01 interface + command: ip -4 a show dev air01 + register: air01_device + changed_when: false + +## this simple test checks if ip address is present in interface description +## TODO filter out the address, derive subnet and compare to expected subnet +- name: verify air02 interface has address in correct network + assert: + that: + - "'10.23.22.' in air01_device.stdout" diff --git a/roles/libvirt-network/tests/vars.yml b/roles/libvirt-network/tests/vars.yml new file mode 100644 index 0000000..4365646 --- /dev/null +++ b/roles/libvirt-network/tests/vars.yml @@ -0,0 +1,32 @@ +libvirt_networks: + - network_action: create + autostart: false + name: oob-net + spec: + bridge: + name: oob-net + stp: 'on' + delay: '0' + ip: + address: "10.23.22.1" + netmask: "255.255.255.0" + dhcp: + - range: + start: 10.23.22.100 + end: 10.23.22.199 + - network_action: create + name: provision-network + spec: + forward: + mode: nat + nat: + port: + - start: 1024 + end: 65535 + bridge: + name: "prov-net-br" + stp: 'on' + delay: '0' + ip: + address: "172.22.0.1" + netmask: "255.255.255.0" \ No newline at end of file diff --git a/roles/libvirt-pool/defaults/main.yml b/roles/libvirt-pool/defaults/main.yml new file mode 100644 index 0000000..0905699 --- /dev/null +++ b/roles/libvirt-pool/defaults/main.yml @@ -0,0 +1,14 @@ +libvirt_pool: + name: airship + path: "/var/lib/airship" +pool_action: create +libvirt_pool_template_default: | + + {{ libvirt_pool.name }} + {% if 'capacity' in libvirt_pool %} + {{ libvirt_pool.capacity }} + {% endif %} + + {{ libvirt_pool.path | default('placeholder_value') }} + + \ No newline at end of file diff --git a/roles/libvirt-pool/tasks/create.yml b/roles/libvirt-pool/tasks/create.yml new file mode 100644 index 0000000..366ce58 --- /dev/null +++ b/roles/libvirt-pool/tasks/create.yml @@ -0,0 +1,24 @@ +--- +- name: Ensure libvirt dir storage pool directories exist + file: + path: "{{ libvirt_pool.path }}" + owner: "{{ libvirt_pool.owner | default(omit) }}" + group: "{{ libvirt_pool.group | default(omit) }}" + mode: "{{ libvirt_pool.mode | default(omit) }}" + state: directory + +- name: Ensure libvirt storage pools are defined + virt_pool: + name: "{{ libvirt_pool.name }}" + command: define + xml: "{{ libvirt_pool.xml | default(libvirt_pool_template_default) }}" + +- name: Ensure libvirt storage pools are active + virt_pool: + name: "{{ libvirt_pool.name }}" + state: active + +- name: Ensure libvirt storage pools are started on boot + virt_pool: + name: "{{ libvirt_pool.name }}" + autostart: yes diff --git a/roles/libvirt-pool/tasks/main.yml b/roles/libvirt-pool/tasks/main.yml new file mode 100644 index 0000000..c073e40 --- /dev/null +++ b/roles/libvirt-pool/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: "{{ pool_action }}.yml" diff --git a/roles/libvirt-pool/templates/pool.xml.j2 b/roles/libvirt-pool/templates/pool.xml.j2 new file mode 100644 index 0000000..36a1983 --- /dev/null +++ b/roles/libvirt-pool/templates/pool.xml.j2 @@ -0,0 +1,9 @@ + + {{ libvirt_pool.name }} + {% if 'capacity' in libvirt_pool %} + {{ libvirt_pool.capacity }} + {% endif %} + + {{ libvirt_pool.path | default('placeholder_value') }} + + \ No newline at end of file diff --git a/roles/libvirt-pool/tests/main.yml b/roles/libvirt-pool/tests/main.yml new file mode 100644 index 0000000..8542b22 --- /dev/null +++ b/roles/libvirt-pool/tests/main.yml @@ -0,0 +1,22 @@ +- name: Include test variables. + include_vars: + file: vars.yml +- name: install libvirt + include_role: + name: libvirt-install +- name: create pool + include_role: + name: libvirt-pool + vars: + ansible_become: true +- name: get pool information + virt_pool: + command: info + become: true + register: storage_pools + +- name: check if pool is available and is at given directory + assert: + that: + - "storage_pools.pools.test_pool.path == '/var/lib/libvirt/my-pool'" + - "storage_pools.pools.test_pool.status == 'running'" diff --git a/roles/libvirt-pool/tests/vars.yml b/roles/libvirt-pool/tests/vars.yml new file mode 100644 index 0000000..8ca7450 --- /dev/null +++ b/roles/libvirt-pool/tests/vars.yml @@ -0,0 +1,3 @@ +libvirt_pool: + path: /var/lib/libvirt/my-pool + name: test_pool \ No newline at end of file diff --git a/roles/libvirt-volume/defaults/main.yml b/roles/libvirt-volume/defaults/main.yml new file mode 100644 index 0000000..ed9089c --- /dev/null +++ b/roles/libvirt-volume/defaults/main.yml @@ -0,0 +1,5 @@ +libvirt_remote_scheme_list: + - http + - https +libvirt_image_cleanup_cache: false +libvirt_image_cache_path: /tmp/airship \ No newline at end of file diff --git a/roles/libvirt-volume/tasks/cleanup.yml b/roles/libvirt-volume/tasks/cleanup.yml new file mode 100644 index 0000000..82a769e --- /dev/null +++ b/roles/libvirt-volume/tasks/cleanup.yml @@ -0,0 +1,4 @@ +- name: Clean up cache directory + file: + path: "{{ libvirt_image_cache_path }}" + state: absent diff --git a/roles/libvirt-volume/tasks/create.yml b/roles/libvirt-volume/tasks/create.yml new file mode 100644 index 0000000..17a96d4 --- /dev/null +++ b/roles/libvirt-volume/tasks/create.yml @@ -0,0 +1,58 @@ +- name: Get Scheme + set_fact: + image_scheme: "{{ libvirt_volume.image | urlsplit('scheme') }}" + when: "libvirt_volume.image is defined" + + +- name: Get Scheme + set_fact: + image_dest: "{{ libvirt_image_cache_path }}/{{ libvirt_volume.image | basename }}" + when: "libvirt_volume.image is defined" + + +- name: Ensure cache directories exist + file: + path: "{{ libvirt_image_cache_path }}" + state: directory + +- name: Ensure remote images are downloaded + get_url: + url: "{{ libvirt_volume.image }}" + dest: "{{ image_dest }}" + checksum: "{{ libvirt_volume.checksum | default(omit) }}" + when: "image_scheme in libvirt_remote_scheme_list and libvirt_volume.image is defined" + +- name: Ensure local images are copied + copy: + src: "{{ libvirt_volume.image }}" + dest: "{{ image_dest }}" + when: "image_scheme not in libvirt_remote_scheme_list and libvirt_volume.image is defined" + +- name: "Create volume" + command: >- + virsh vol-create-as "{{ libvirt_volume.pool }}" \ + --name "{{ libvirt_volume.name }}" \ + --capacity "{{ libvirt_volume.size }}" \ + --format "{{ libvirt_volume.format | default('qcow2') }}" + register: libvirt_create_volume + failed_when: + - "libvirt_create_volume.rc != 0" + - "'Failed to create vol' in libvirt_create_volume.stdout" + - "'exists already' not in libvirt_create_volume.stdout" + changed_when: + - "libvirt_create_volume.rc != 1" + - "'exists already' not in libvirt_create_volume.stdout" + +- name: "Upload volume from downloaded image" + command: >- + virsh vol-upload --pool "{{ libvirt_volume.pool }}" --vol "{{ libvirt_volume.name }}" --file "{{ image_dest }}" + when: + - "libvirt_volume.image is defined" + - "libvirt_create_volume.rc == 0" + +- name: "Resize volume after uploading from image" + command: >- + virsh vol-resize --vol "{{ libvirt_volume.name }}" --pool "{{ libvirt_volume.pool }}" --capacity "{{ libvirt_volume.size }}" + when: + - "libvirt_create_volume.rc == 0" + - "libvirt_volume.image is defined" diff --git a/roles/libvirt-volume/tasks/main.yml b/roles/libvirt-volume/tasks/main.yml new file mode 100644 index 0000000..80cfb44 --- /dev/null +++ b/roles/libvirt-volume/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: "{{ volume_action }}.yml" diff --git a/roles/libvirt-volume/tests/main.yml b/roles/libvirt-volume/tests/main.yml new file mode 100644 index 0000000..467b499 --- /dev/null +++ b/roles/libvirt-volume/tests/main.yml @@ -0,0 +1,33 @@ +- name: Include test variables. + include_vars: + file: vars.yml +- name: install libvirt + include_role: + name: libvirt-install +- name: create pool + include_role: + name: libvirt-pool + vars: + ansible_become: true +- name: Create defined volumes + include_role: + name: libvirt-volume + with_items: "{{ libvirt_volumes }}" + vars: + libvirt_volume: "{{ vol }}" + volume_action: "{{ vol.action }}" + ansible_become: true + loop_control: + loop_var: vol +- name: save volume list + command: virsh vol-list --pool {{ libvirt_pool.name }} + register: libvirt_pool_list + changed_when: false + become: true +- name: verify volumes exist + assert: + that: + - "vol.name in libvirt_pool_list.stdout" + with_items: "{{ libvirt_volumes }}" + loop_control: + loop_var: vol \ No newline at end of file diff --git a/roles/libvirt-volume/tests/vars.yml b/roles/libvirt-volume/tests/vars.yml new file mode 100644 index 0000000..2024f0f --- /dev/null +++ b/roles/libvirt-volume/tests/vars.yml @@ -0,0 +1,14 @@ +libvirt_pool: + path: /var/lib/libvirt/airship + name: airship + +libvirt_volumes: + - name: volume-1 + image: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create + - name: volume-2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create \ No newline at end of file diff --git a/roles/redfish-emulator/defaults/main.yml b/roles/redfish-emulator/defaults/main.yml new file mode 100644 index 0000000..93d0cba --- /dev/null +++ b/roles/redfish-emulator/defaults/main.yml @@ -0,0 +1,3 @@ +redfish_action: install +redfish_emulator_bind_ip: 127.0.0.1 +redfish_emulator_bind_port: 8000 \ No newline at end of file diff --git a/roles/redfish-emulator/handlers/main.yml b/roles/redfish-emulator/handlers/main.yml new file mode 100644 index 0000000..3c2f75f --- /dev/null +++ b/roles/redfish-emulator/handlers/main.yml @@ -0,0 +1,11 @@ +- name: reload systemd configuration + become: yes + systemd: + daemon_reload: yes + +- name: restart sushy-emulator + become: yes + service: + name: sushy-tools + state: restarted + enabled: true \ No newline at end of file diff --git a/roles/redfish-emulator/tasks/install.yml b/roles/redfish-emulator/tasks/install.yml new file mode 100644 index 0000000..f7e6582 --- /dev/null +++ b/roles/redfish-emulator/tasks/install.yml @@ -0,0 +1,37 @@ +- block: + - name: Ensuring python3-pip and support packages are present + when: ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux' + fail: + msg: "CentoOS or RHEL is not currently supported" + + - name: Ensuring python3-pip and support packages are present + become: true + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' + apt: + name: + - python3-pip + - python3-libvirt + - python-libvirt + state: present + + - name: Install sushy-tools + pip: + name: sushy-tools + executable: pip3 + become: true + +- name: install systemd sushy service unit + become: true + template: + src: sushy-tools.service.j2 + dest: /etc/systemd/system/sushy-tools.service + notify: + - reload systemd configuration + - restart sushy-emulator + +- name: start sushy-emulator service + become: true + service: + name: sushy-tools + state: started + enabled: true diff --git a/roles/redfish-emulator/tasks/main.yml b/roles/redfish-emulator/tasks/main.yml new file mode 100644 index 0000000..ef84389 --- /dev/null +++ b/roles/redfish-emulator/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: "{{ redfish_action }}.yml" diff --git a/roles/redfish-emulator/templates/sushy-tools.service.j2 b/roles/redfish-emulator/templates/sushy-tools.service.j2 new file mode 100644 index 0000000..db13202 --- /dev/null +++ b/roles/redfish-emulator/templates/sushy-tools.service.j2 @@ -0,0 +1,15 @@ +# This file is part of sushy-emulator (redfish). +# + +[Unit] +Description=Sushy Libvirt emulator +After=syslog.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/sushy-emulator -i {{ redfish_emulator_bind_ip }} -p {{ redfish_emulator_bind_port }} --libvirt-uri "qemu:///system" +StandardOutput=syslog +StandardError=syslog + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/redfish-emulator/tests/main.yml b/roles/redfish-emulator/tests/main.yml new file mode 100644 index 0000000..46988f4 --- /dev/null +++ b/roles/redfish-emulator/tests/main.yml @@ -0,0 +1,40 @@ +- name: Include test variables. + include_vars: + file: vars.yml +- name: install libvirt + include_role: + name: libvirt-install +- name: create pool + include_role: + name: libvirt-pool + vars: + ansible_become: true +- name: Create defined volumes + include_role: + name: libvirt-volume + with_items: "{{ libvirt_volumes }}" + vars: + libvirt_volume: "{{ vol }}" + volume_action: "{{ vol.action }}" + ansible_become: true + loop_control: + loop_var: vol +- name: create libvirt domains + include_role: + name: libvirt-domain +- name: install sushy-tools + include_role: + name: redfish-emulator +- name: query redfish to make sure it has runnig domains + uri: + url: http://localhost:8000/redfish/v1/Systems?format=json + method: GET + return_content: yes + register: sushy_response +- name: debug redfish machines + debug: + var: sushy_response +- name: verify that virtual machine is present in sushy tools + assert: + that: + - sushy_response.json["Members@odata.count"] == 1 diff --git a/roles/redfish-emulator/tests/vars.yml b/roles/redfish-emulator/tests/vars.yml new file mode 100644 index 0000000..11b9852 --- /dev/null +++ b/roles/redfish-emulator/tests/vars.yml @@ -0,0 +1,44 @@ +libvirt_pool: + path: /var/lib/libvirt/airship + name: airship + +libvirt_volumes: + - name: volume-1 + image: https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create + - name: volume-2 + size: 10G + pool: "{{ libvirt_pool.name }}" + action: create + +libvirt_domain: + state: running + name: 'vm1' + memory_mb: 2048 + vcpus: 1 + volumes: + - name: 'volume-1' + device: 'disk' + format: 'qcow2' + pool: 'airship' + interfaces: + - network: 'provision-network' + +libvirt_network: + name: provision-network + spec: + forward: + mode: nat + nat: + port: + - start: 1024 + end: 65535 + bridge: + name: "prov-net-br" + stp: 'on' + delay: '0' + ip: + address: "172.22.0.1" + netmask: "255.255.255.0" \ No newline at end of file diff --git a/tests/ansible/lint.yaml b/tests/ansible/lint.yml similarity index 85% rename from tests/ansible/lint.yaml rename to tests/ansible/lint.yml index f62ef67..95bf322 100644 --- a/tests/ansible/lint.yaml +++ b/tests/ansible/lint.yml @@ -14,20 +14,19 @@ state: present when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' - - name: find files to lint find: paths: - - "{{ src_dir| default('../..') }}/playbooks" - - "{{ src_dir| default('../..') }}/roles" + - "{{ zuul.project.src_dir }}/playbooks" + - "{{ zuul.project.src_dir }}/roles" patterns: - "*.yaml" - "*.yml" + recurse: true register: files_to_lint # TODO (kkalynovskyi) develop suitable ansible-lint configuration - name: run ansible-lint against found files command: "ansible-lint {{ item.path }}" with_items: "{{ files_to_lint.files }}" - - + changed_when: false diff --git a/tests/ansible/role-test-runner.yml b/tests/ansible/role-test-runner.yml new file mode 100644 index 0000000..bb14bf1 --- /dev/null +++ b/tests/ansible/role-test-runner.yml @@ -0,0 +1,16 @@ +--- +- hosts: primary + tasks: + - name: set default roles + set_fact: + test_subject_roles_default: + - libvirt-network + - libvirt-pool + - libvirt-volume + - libvirt-domain + - redfish-emulator + - name: run tests against defined roles + include_tasks: "../../roles/{{ role_name }}/tests/main.yml" + with_items: "{{ test_subject_roles | default(test_subject_roles_default) }}" + loop_control: + loop_var: role_name diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index 7764817..cae4d1f 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -12,5 +12,16 @@ - job: name: ansible-lint-airship - run: tests/ansible/lint.yaml - nodeset: ubuntu-single-airship \ No newline at end of file + run: tests/ansible/lint.yml + nodeset: ubuntu-single-airship + +- job: + name: zuul-airship-roles-test-libvirt + run: tests/ansible/role-test-runner.yml + vars: + test_subject_roles: + - libvirt-network + - libvirt-pool + - libvirt-volume + - libvirt-domain + nodeset: ubuntu-single-airship diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 0e5189b..67f4915 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -2,6 +2,8 @@ check: jobs: - ansible-lint-airship + - zuul-airship-roles-test-libvirt gate: jobs: - ansible-lint-airship + - zuul-airship-roles-test-libvirt