├── .github ├── CODEOWNERS └── workflows │ └── publish-role.yml ├── tests ├── inventory └── test.yml ├── tasks ├── destroy-volumes.yml ├── check-usb-devices.yml ├── check-interface.yml ├── vm.yml ├── destroy-vm.yml ├── volumes.yml ├── main.yml └── autodetect.yml ├── meta └── main.yml ├── vars ├── RedHat.yml ├── Archlinux.yml └── Debian.yml ├── files ├── destroy_virt_volume.sh └── virt_volume.sh ├── defaults └── main.yml ├── templates └── vm.xml.j2 └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @stackhpc/ansible 2 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | localhost ansible_connection='local' ansible_python_interpreter='/usr/bin/env python' 2 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | connection: local 4 | roles: 5 | - stackhpc.libvirt-vm 6 | -------------------------------------------------------------------------------- /.github/workflows/publish-role.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish Ansible Role 3 | 'on': 4 | push: 5 | tags: 6 | - "v?[0-9]+.[0-9]+.[0-9]+" 7 | workflow_dispatch: 8 | jobs: 9 | publish_role: 10 | uses: stackhpc/.github/.github/workflows/publish-role.yml@main 11 | secrets: 12 | GALAXY_API_KEY: ${{ secrets.GALAXY_API_KEY }} 13 | -------------------------------------------------------------------------------- /tasks/destroy-volumes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure the VM volumes do not exist 3 | ansible.builtin.script: > 4 | destroy_virt_volume.sh 5 | {{ item.name }} 6 | {{ item.pool | default('default') }} 7 | with_items: "{{ volumes }}" 8 | when: item.type | default(libvirt_volume_default_type) == 'volume' 9 | register: volume_result 10 | environment: "{{ libvirt_vm_script_env }}" 11 | changed_when: 12 | - volume_result is success 13 | - (volume_result.stdout | from_json).changed | default(True) 14 | become: true 15 | -------------------------------------------------------------------------------- /tasks/check-usb-devices.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: List USB hardware 3 | ansible.builtin.command: lsusb -d {{ usb_device.vendor }}:{{ usb_device.product }} 4 | register: host_attached_usb_device 5 | become: true 6 | changed_when: false 7 | failed_when: false 8 | 9 | - name: Check USB device is present on Host system 10 | ansible.builtin.fail: 11 | msg: > 12 | The USB Device with Vendor ID:{{ usb_device.vendor }} and Product ID:{{ usb_device.product }} is not seen on host system 13 | Is the USB device plugged in correctly ? 14 | when: 15 | - host_attached_usb_device.rc != 0 16 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | # role_name: libvirt_vm 4 | author: Mark Goddard 5 | description: > 6 | Role to configure and create VMs on a Libvirt/KVM hypervisor 7 | company: StackHPC Ltd 8 | license: Apache2 9 | min_ansible_version: 2.0 10 | platforms: 11 | - name: EL 12 | versions: 13 | - 7 14 | - name: Ubuntu 15 | versions: 16 | - all 17 | - name: Debian 18 | versions: 19 | - all 20 | - name: ArchLinux 21 | versions: 22 | - all 23 | galaxy_tags: 24 | - cloud 25 | - kvm 26 | - libvirt 27 | - vm 28 | 29 | dependencies: [] 30 | -------------------------------------------------------------------------------- /vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Who owns the serial console logs in console_log_path 4 | libvirt_vm_log_owner: qemu 5 | 6 | # The environment passed to virt_volume.sh 7 | libvirt_vm_script_env_redhat: 8 | VOLUME_GROUP: qemu 9 | VOLUME_OWNER: qemu 10 | 11 | libvirt_vm_script_env: >- 12 | {{ libvirt_vm_script_env_redhat | combine(libvirt_vm_virsh_default_env) }} 13 | 14 | # Path to template OVMF efi variable store. A copy will be created 15 | # for each VM created. 16 | libvirt_vm_ovmf_efi_variable_store_path: /usr/share/edk2/ovmf/OVMF_VARS.fd 17 | 18 | # Format of the template OVMF efi variable store. 19 | libvirt_vm_ovmf_efi_variable_store_format: raw 20 | 21 | # Path to OVMF efi firmware 22 | libvirt_vm_ovmf_efi_firmware_path: /usr/share/edk2/ovmf/OVMF_CODE.cc.fd 23 | -------------------------------------------------------------------------------- /tasks/check-interface.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check network interface has a network name 3 | ansible.builtin.fail: 4 | msg: > 5 | The interface definition {{ interface }} has type 'network', but does not have 6 | a network name defined. 7 | when: 8 | - interface.type is not defined or 9 | interface.type == 'network' 10 | - interface.network is not defined 11 | 12 | - name: Check direct interface has an interface device name 13 | ansible.builtin.fail: 14 | msg: > 15 | The interface definition {{ interface }} has type 'direct', but does not have 16 | a host source device defined. 17 | when: 18 | - interface.type is defined 19 | - interface.type == 'direct' 20 | - interface.source is not defined or 21 | interface.source.dev is not defined 22 | -------------------------------------------------------------------------------- /vars/Archlinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Who owns the serial console logs in console_log_path 3 | libvirt_vm_log_owner: root 4 | 5 | # The environment passed to virt_volume.sh 6 | libvirt_vm_script_env_arch: {} 7 | 8 | libvirt_vm_script_env: >- 9 | {{ libvirt_vm_script_env_arch | combine(libvirt_vm_virsh_default_env) }} 10 | 11 | # Archlinux qemu comes with kvm support compiled in 12 | libvirt_vm_emulator: /usr/bin/qemu-system-x86_64 13 | 14 | # Path to template OVMF efi variable store. A copy will be created 15 | # for each VM created. 16 | libvirt_vm_ovmf_efi_variable_store_path: /usr/share/OVMF/OVMF_VARS.fd 17 | 18 | # Format of the template OVMF efi variable store. 19 | libvirt_vm_ovmf_efi_variable_store_format: raw 20 | 21 | # Path to OVMF efi firmware 22 | libvirt_vm_ovmf_efi_firmware_path: /usr/share/OVMF/OVMF_CODE.fd 23 | -------------------------------------------------------------------------------- /vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Who owns the serial console logs in console_log_path 4 | libvirt_vm_log_owner: libvirt-qemu 5 | 6 | # The environment passed to virt_volume.sh 7 | libvirt_vm_script_env_debian: 8 | VOLUME_GROUP: libvirt-qemu 9 | VOLUME_OWNER: libvirt-qemu 10 | 11 | libvirt_vm_script_env: >- 12 | {{ libvirt_vm_script_env_debian | combine(libvirt_vm_virsh_default_env) }} 13 | 14 | # Path to template OVMF efi variable store. A copy will be created 15 | # for each VM created. 16 | libvirt_vm_ovmf_efi_variable_store_path: "/usr/share/OVMF/{{ 'OVMF_VARS_4M.fd' if ansible_facts.distribution_release == 'noble' else 'OVMF_VARS.fd' }}" 17 | 18 | # Format of the template OVMF efi variable store. 19 | libvirt_vm_ovmf_efi_variable_store_format: raw 20 | 21 | # Path to OVMF efi firmware 22 | libvirt_vm_ovmf_efi_firmware_path: "/usr/share/OVMF/{{ 'OVMF_CODE_4M.fd' if ansible_facts.distribution_release == 'noble' else 'OVMF_CODE.fd' }}" 23 | -------------------------------------------------------------------------------- /tasks/vm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure the VM console log directory exists 3 | ansible.builtin.file: 4 | path: "{{ console_log_path | dirname }}" 5 | state: directory 6 | owner: "{{ libvirt_vm_log_owner }}" 7 | group: "{{ libvirt_vm_log_owner }}" 8 | recurse: true 9 | mode: 0770 10 | when: console_log_enabled | bool 11 | become: "{{ libvirt_vm_sudo }}" 12 | 13 | - name: Validate VM interfaces 14 | ansible.builtin.include_tasks: check-interface.yml 15 | vars: 16 | interface: "{{ item }}" 17 | with_items: "{{ interfaces }}" 18 | 19 | - name: Validate Host USB Devices 20 | ansible.builtin.include_tasks: check-usb-devices.yml 21 | vars: 22 | usb_device: "{{ item }}" 23 | with_items: "{{ usb_devices }}" 24 | 25 | - name: Ensure the VM is defined 26 | community.libvirt.virt: 27 | command: define 28 | xml: "{{ lookup('template', vm.xml_file | default('vm.xml.j2')) }}" 29 | uri: "{{ libvirt_vm_uri | default(omit, true) }}" 30 | become: "{{ libvirt_vm_sudo }}" 31 | 32 | - name: Ensure the VM is running and started at boot 33 | community.libvirt.virt: 34 | name: "{{ vm.name }}" 35 | autostart: "{{ autostart | bool }}" 36 | state: "{{ 'running' if (start | bool) else 'shutdown' }}" 37 | uri: "{{ libvirt_vm_uri | default(omit, true) }}" 38 | become: "{{ libvirt_vm_sudo }}" 39 | -------------------------------------------------------------------------------- /tasks/destroy-vm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # The destroyed state does not seem to be idempotent, so check whether the VM 3 | # exists before destroying it. 4 | - name: Check the VM's status 5 | community.libvirt.virt: 6 | name: "{{ vm.name }}" 7 | command: list_vms 8 | uri: "{{ libvirt_vm_uri | default(omit, true) }}" 9 | register: result 10 | become: true 11 | 12 | - name: Destory the VM is existing 13 | block: 14 | - name: Ensure the VM is absent 15 | community.libvirt.virt: 16 | name: "{{ vm.name }}" 17 | state: destroyed 18 | uri: "{{ libvirt_vm_uri | default(omit, true) }}" 19 | become: true 20 | 21 | # note(wszumski): the virt module does not seem to support 22 | # removing vms with nvram defined - as a workaround, use the 23 | # virsh cli directly. It may be better to detect if dumpxml 24 | # actually contains an nvram element rather than relying on 25 | # boot_firmware having the correct value. 26 | - name: Ensure the VM is undefined 27 | ansible.builtin.command: 28 | cmd: >- 29 | virsh 30 | {% if libvirt_vm_uri %}-c {{ libvirt_vm_uri }}{% endif %} 31 | undefine 32 | {% if boot_firmware == 'efi' %} --nvram{% endif %} 33 | {{ vm.name }} 34 | become: true 35 | changed_when: true 36 | when: vm.name in result.list_vms 37 | -------------------------------------------------------------------------------- /files/destroy_virt_volume.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2017 StackHPC Ltd. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | # Ensure that a libvirt volume does not exists. 18 | # On success, output a JSON object with a 'changed' item. 19 | 20 | if [[ $# -ne 2 ]]; then 21 | echo "Usage: $0 " 22 | exit 1 23 | fi 24 | 25 | NAME=$1 26 | POOL=$2 27 | 28 | # Check whether a volume with this name exists. 29 | output=$(virsh vol-info --pool $POOL --vol $NAME 2>&1) 30 | result=$? 31 | if [[ $result -ne 0 ]]; then 32 | if echo "$output" | grep 'Storage volume not found' >/dev/null 2>&1; then 33 | echo '{"changed": false}' 34 | exit 0 35 | else 36 | echo "Unexpected error while getting volume info" 37 | echo "$output" 38 | exit $result 39 | fi 40 | fi 41 | 42 | # Delete the volume. 43 | output=$(virsh vol-delete --pool $POOL --vol $NAME 2>&1) 44 | result=$? 45 | if [[ $result -ne 0 ]]; then 46 | echo "Failed to delete volume" 47 | echo "$output" 48 | exit $result 49 | fi 50 | 51 | echo '{"changed": true}' 52 | exit 0 53 | -------------------------------------------------------------------------------- /tasks/volumes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure remote images are downloaded 3 | ansible.builtin.get_url: 4 | url: "{{ item.image }}" 5 | dest: "{{ libvirt_vm_image_cache_path }}/{{ item.image | basename }}" 6 | checksum: "{{ item.checksum | default(omit) }}" 7 | with_items: "{{ volumes | selectattr('image', 'defined') | list }}" 8 | when: "'http' in item.image" 9 | 10 | - name: Ensure local images are copied 11 | ansible.builtin.copy: 12 | src: "{{ item.image }}" 13 | dest: "{{ libvirt_vm_image_cache_path }}/{{ item.image | basename }}" 14 | checksum: "{{ item.checksum | default(omit) }}" 15 | remote_src: "{{ item.remote_src | default(true) | bool }}" 16 | with_items: "{{ volumes | selectattr('image', 'defined') | list }}" 17 | when: "'http' not in item.image" 18 | 19 | - name: Ensure the VM disk volumes exist 20 | ansible.builtin.script: > 21 | virt_volume.sh 22 | -n {{ item.name }} 23 | -p {{ item.pool |default('default') }} 24 | -c {{ item.capacity }} 25 | -f {{ item.format | default(libvirt_volume_default_format) }} 26 | {% if item.image is defined %} 27 | -i {{ libvirt_vm_image_cache_path }}/{{ item.image | basename }} 28 | {% elif item.backing_image is defined %} 29 | -b {{ item.backing_image }} 30 | {% endif %} 31 | -a {{ ansible_check_mode }} 32 | with_items: "{{ volumes }}" 33 | when: item.type | default(libvirt_volume_default_type) == 'volume' 34 | environment: "{{ libvirt_vm_script_env }}" 35 | register: volume_result 36 | changed_when: 37 | - volume_result is success 38 | - (volume_result.stdout | from_json).changed | default(True) 39 | check_mode: false 40 | become: true 41 | 42 | - name: Ensure the VM network volumes exist 43 | ansible.builtin.command: qemu-img create -f {{ item.source.protocol }} {{ item.source.protocol }}:{{ item.source.name }} {{ item.capacity }} 44 | with_items: "{{ volumes }}" 45 | when: item.type | default(libvirt_volume_default_type) == 'network' 46 | register: volume_result_network 47 | # 0 is OK, 1 is an existing image 48 | failed_when: volume_result_network.rc >= 2 49 | changed_when: 50 | - volume_result_network is success 51 | - volume_result_network.rc == 1 52 | check_mode: false 53 | become: true 54 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Gather os specific variables 3 | ansible.builtin.include_vars: "{{ item }}" 4 | with_first_found: 5 | - files: 6 | - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" 7 | - "{{ ansible_facts.distribution }}.yml" 8 | - "{{ ansible_facts.os_family }}.yml" 9 | tags: vars 10 | 11 | - ansible.builtin.include_tasks: autodetect.yml 12 | # We don't need to know the engine and emulator if we're not creating any new 13 | # VMs. 14 | when: >- 15 | (libvirt_vms | selectattr('state', 'defined') 16 | | selectattr('state', 'equalto', 'absent') | list) != libvirt_vms 17 | 18 | # Libvirt requires qemu-img to create qcow2 files. 19 | - name: Ensure qemu-img and ovmf is installed 20 | vars: 21 | is_efi: >- 22 | (libvirt_vms | selectattr('state', 'defined') 23 | | selectattr('state', 'equalto', 'absent') | 24 | | selectattr('boot_firmware', 'equalto', 'efi')) | bool 25 | packages: 26 | - "{{ 'qemu-img' if ansible_facts.os_family == 'RedHat' else 'qemu-utils' }}" 27 | - "{{ 'ovmf' if ansible_facts.os_family == 'Debian' and is_efi }}" 28 | ansible.builtin.package: 29 | name: "{{ packages | select | unique | list }}" 30 | update_cache: "{{ True if ansible_facts.pkg_mgr == 'apt' else omit }}" 31 | become: true 32 | 33 | - include_tasks: volumes.yml 34 | vars: 35 | volumes: "{{ vm.volumes | default([], true) }}" 36 | with_items: "{{ libvirt_vms }}" 37 | loop_control: 38 | loop_var: vm 39 | when: (vm.state | default('present', true)) == 'present' 40 | 41 | - include_tasks: vm.yml 42 | vars: 43 | console_log_enabled: "{{ vm.console_log_enabled | default(false) }}" 44 | console_log_path: >- 45 | {{ vm.console_log_path | 46 | default(libvirt_vm_default_console_log_dir + '/' + vm.name + '-console.log', true) }} 47 | machine_default: "{{ none if libvirt_vm_engine == 'kvm' else 'pc' }}" 48 | machine: "{{ vm.machine | default(machine_default, true) }}" 49 | cpu_mode: "{{ vm.cpu_mode | default(libvirt_cpu_mode_default) }}" 50 | volumes: "{{ vm.volumes | default([], true) }}" 51 | interfaces: "{{ vm.interfaces | default([], true) }}" 52 | usb_devices: "{{ vm.usb_devices | default([], false) }}" 53 | start: "{{ vm.start | default(true) }}" 54 | autostart: "{{ vm.autostart | default(true) }}" 55 | enable_vnc: "{{ vm.enable_vnc | default(false) }}" 56 | enable_spice: "{{ vm.enable_spice | default(false) }}" 57 | enable_guest_virtio: "{{ vm.enable_guest_virtio | default(false) }}" 58 | boot_firmware: "{{ vm.boot_firmware | default('bios', true) | lower }}" 59 | with_items: "{{ libvirt_vms }}" 60 | loop_control: 61 | loop_var: vm 62 | when: (vm.state | default('present', true)) == 'present' 63 | 64 | - include_tasks: destroy-vm.yml 65 | vars: 66 | boot_firmware: "{{ vm.boot_firmware | default('bios', true) | lower }}" 67 | with_items: "{{ libvirt_vms }}" 68 | loop_control: 69 | loop_var: vm 70 | when: (vm.state | default('present', true)) == 'absent' 71 | 72 | - include_tasks: destroy-volumes.yml 73 | vars: 74 | volumes: "{{ vm.volumes | default([], true) }}" 75 | with_items: "{{ libvirt_vms }}" 76 | loop_control: 77 | loop_var: vm 78 | when: (vm.state | default('present', true)) == 'absent' 79 | -------------------------------------------------------------------------------- /tasks/autodetect.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Detect the virtualisation engine 3 | block: 4 | - name: Load the kvm kernel module 5 | modprobe: 6 | name: kvm 7 | become: true 8 | failed_when: false 9 | 10 | - name: Check for the KVM device 11 | ansible.builtin.stat: 12 | path: /dev/kvm 13 | register: stat_kvm 14 | 15 | - name: Set a fact containing the virtualisation engine 16 | ansible.builtin.set_fact: 17 | libvirt_vm_engine: >- 18 | {%- if ansible_facts.architecture != libvirt_vm_arch -%} 19 | {# Virtualisation instructions are generally available only for the host 20 | architecture. Ideally we would test for virtualisation instructions, eg. vt-d 21 | as it is possible that another architecture could support these even 22 | if the emulated cpu architecture is not the same. #} 23 | qemu 24 | {%- elif stat_kvm.stat.exists -%} 25 | kvm 26 | {%- else -%} 27 | qemu 28 | {%- endif -%} 29 | when: libvirt_vm_engine is none or libvirt_vm_engine | length == 0 30 | 31 | - name: Detect the virtualisation emulator 32 | block: 33 | - name: Set fact when vm engine is kvm 34 | block: 35 | - name: Detect the KVM emulator binary path 36 | ansible.builtin.stat: 37 | path: "{{ item }}" 38 | register: kvm_emulator_result 39 | with_items: 40 | - /usr/bin/kvm 41 | - /usr/bin/qemu-kvm 42 | - /usr/libexec/qemu-kvm 43 | 44 | - name: Set a fact containing the KVM emulator binary path 45 | ansible.builtin.set_fact: 46 | libvirt_vm_emulator: "{{ item.item }}" 47 | with_items: "{{ kvm_emulator_result.results }}" 48 | when: item.stat.exists 49 | when: libvirt_vm_engine == 'kvm' 50 | 51 | - name: Set a fact when RedHat and vm engine is qemu 52 | block: 53 | - name: Detect the QEMU emulator binary path 54 | ansible.builtin.stat: 55 | path: /usr/libexec/qemu-kvm 56 | register: kvm_emulator_result 57 | 58 | - name: Set a fact containing the QEMU emulator binary path 59 | ansible.builtin.set_fact: 60 | libvirt_vm_emulator: "{{ kvm_emulator_result.stat.path }}" 61 | when: kvm_emulator_result.stat.exists 62 | when: 63 | - libvirt_vm_engine == 'qemu' 64 | - ansible_facts.os_family == 'RedHat' 65 | - ansible_facts.distribution_major_version | int >= 8 66 | 67 | - name: Set a fact when not RedHat and vm engine is qemu 68 | block: 69 | - name: Detect the QEMU emulator binary path 70 | ansible.builtin.shell: which qemu-system-{{ libvirt_vm_arch }} 71 | register: qemu_emulator_result 72 | changed_when: false 73 | 74 | - name: Set a fact containing the QEMU emulator binary path 75 | ansible.builtin.set_fact: 76 | libvirt_vm_emulator: "{{ qemu_emulator_result.stdout }}" 77 | 78 | when: 79 | - libvirt_vm_engine == 'qemu' 80 | - ansible_facts.os_family != 'RedHat' or ansible_facts.distribution_major_version | int == 7 81 | 82 | - name: Fail if unable to detect the emulator 83 | ansible.builtin.fail: 84 | msg: Unable to detect emulator for engine {{ libvirt_vm_engine }}. 85 | when: libvirt_vm_emulator is none 86 | when: libvirt_vm_emulator is none or libvirt_vm_emulator | length == 0 87 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # The default directory in which to store VM console logs, if a VM-specific log 4 | # file path is not given. 5 | libvirt_vm_default_console_log_dir: "/var/log/libvirt-consoles" 6 | 7 | # Whether UUID should be calculated by hashing the VM name. If not, the UUID is 8 | # randomly generated by libvirt when the VM is defined. 9 | libvirt_vm_default_uuid_deterministic: false 10 | 11 | # The default location for libvirt images 12 | libvirt_volume_default_images_path: '/var/lib/libvirt/images' 13 | 14 | # Default type for Libvirt volumes 15 | libvirt_volume_default_type: volume 16 | 17 | # The default format for Libvirt volumes. 18 | libvirt_volume_default_format: qcow2 19 | 20 | # The default device for Libvirt volumes. 21 | libvirt_volume_default_device: disk 22 | 23 | # Path to cache downloaded images. 24 | libvirt_vm_image_cache_path: "{{ libvirt_image_cache_path }}" 25 | 26 | # NOTE(mgoddard): Temporarily support this name in addition to 27 | # 'libvirt_vm_image_cache_path'. 28 | # TODO(mgoddard): Remove this for the next major release. 29 | libvirt_image_cache_path: "/tmp/" 30 | 31 | # CPU architecture. 32 | libvirt_vm_arch: x86_64 33 | 34 | # Virtualisation engine. If not set, the role will attempt to auto-detect the 35 | # optimal engine to use. 36 | libvirt_vm_engine: 37 | 38 | # Path to emulator binary. If not set, the role will attempt to auto-detect the 39 | # correct emulator to use. 40 | libvirt_vm_emulator: 41 | 42 | # Default value for clock syncing. The default (false) uses 43 | # to configure the instances clock synchronisation. Change to a timezone to make 44 | # configuration use 45 | libvirt_vm_clock_offset: false 46 | 47 | # Default value for whether to trust guest receive filters. This gets mapped to 48 | # the trustGuestRxFilters attribute of VM interfaces. 49 | libvirt_vm_trust_guest_rx_filters: false 50 | 51 | # A list of specifications of VMs to be created. 52 | # For backwards compatibility, libvirt_vms defaults to a singleton list using 53 | # the values of the deprecated variables below. 54 | # See README.md or tasks/main.yml for these attributes' defaults. 55 | libvirt_vms: 56 | # State of the VM. May be 'present' or 'absent'. 57 | - state: "{{ libvirt_vm_state }}" 58 | 59 | # Name of the VM. 60 | name: "{{ libvirt_vm_name }}" 61 | 62 | # Memory in MB. 63 | memory_mb: "{{ libvirt_vm_memory_mb }}" 64 | 65 | # Number of vCPUs. 66 | vcpus: "{{ libvirt_vm_vcpus }}" 67 | 68 | # Virtual machine type. 69 | machine: "{{ libvirt_vm_machine }}" 70 | 71 | # Virtual machine CPU mode. 72 | cpu_mode: "{{ libvirt_vm_cpu_mode | default(libvirt_cpu_mode_default, true) }}" 73 | 74 | # List of volumes. 75 | volumes: "{{ libvirt_vm_volumes }}" 76 | 77 | # What time should the clock be synced to on boot (utc/localtime/timezone/variable) 78 | clock_offset: "localtime" 79 | 80 | # List of network interfaces. 81 | interfaces: "{{ libvirt_vm_interfaces }}" 82 | 83 | # Path to console log file. 84 | console_log_path: "{{ libvirt_vm_console_log_path }}" 85 | 86 | # XML template file to source domain definition 87 | xml_file: vm.xml.j2 88 | 89 | # May be one of: bios, or efi. 90 | boot_firmware: bios 91 | 92 | # Variables to add to the enviroment that is used to execute virsh commands 93 | libvirt_vm_virsh_default_env: "{{ { 'LIBVIRT_DEFAULT_URI': libvirt_vm_uri } if libvirt_vm_uri else {} }}" 94 | 95 | # Override for the libvirt connection uri. Leave unset to use the default. 96 | libvirt_vm_uri: "" 97 | 98 | # Whether to use sudo with libvirt commands, this can be disabled with qemu:///session 99 | # to create VMs as an unprivileged user 100 | libvirt_vm_sudo: true 101 | 102 | # Default CPU mode if libvirt_vm_cpu_mode or vm.cpu_mode is undefined 103 | libvirt_cpu_mode_default: "{{ 'host-passthrough' if libvirt_vm_engine == 'kvm' else 'host-model' }}" 104 | 105 | ### DEPRECATED ### 106 | # Use the above settings for each item within `libvirt_vms`, instead of the 107 | # below deprecated variables. 108 | 109 | libvirt_vm_state: 110 | libvirt_vm_machine: 111 | libvirt_vm_cpu_mode: 112 | libvirt_vm_volumes: 113 | libvirt_vm_interfaces: 114 | libvirt_vm_console_log_path: 115 | # Required, so we should fail if not set. 116 | # libvirt_vm_name: 117 | # libvirt_vm_memory_mb: 118 | # libvirt_vm_vcpus: 119 | -------------------------------------------------------------------------------- /files/virt_volume.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2017 StackHPC Ltd. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | # Ensure that a libvirt volume exists, optionally uploading an image. 18 | # On success, output a JSON object with a 'changed' item. 19 | 20 | # Parse options 21 | OPTIND=1 22 | 23 | while getopts ":n:p:c:f:i:b:a:" opt; do 24 | case ${opt} in 25 | n) NAME=$OPTARG;; 26 | p) POOL=$OPTARG;; 27 | c) CAPACITY=$OPTARG;; 28 | f) FORMAT=$OPTARG;; 29 | i) IMAGE=$OPTARG;; 30 | b) BACKING_IMAGE=$OPTARG;; 31 | a) CHECK=$OPTARG;; 32 | \?) 33 | echo "Invalid option: -$OPTARG" >&2 34 | exit 1 35 | ;; 36 | :) 37 | echo "Option -$OPTARG requires an argument." >&2 38 | exit 1 39 | ;; 40 | esac 41 | done 42 | 43 | # Check options 44 | if ! [[ -n $NAME && -n $POOL && -n $CAPACITY ]]; then 45 | echo "Missing manditory options" >&2 46 | echo "Usage: $0 -n -p -c [-f ] [-i | -b ]" 47 | exit 1 48 | fi 49 | if [[ -n $IMAGE && -n $BACKING_IMAGE ]]; then 50 | echo "Options -i and -b are mutually exclusive" >&2 51 | exit 1 52 | fi 53 | if [[ -z "$FORMAT" ]]; then 54 | FORMAT='qcow2' 55 | fi 56 | 57 | 58 | # Check whether a volume with this name exists. 59 | output=$(virsh vol-info --pool "$POOL" --vol "$NAME" 2>&1) 60 | result=$? 61 | if [[ $result -eq 0 ]]; then 62 | echo '{"changed": false}' 63 | exit 0 64 | fi 65 | 66 | # Stop here in check mode, there will be a change 67 | if [[ $CHECK = "True" ]]; then 68 | echo '{"changed": true}' 69 | exit 0 70 | fi 71 | 72 | # Create the volume. 73 | if [[ -n $BACKING_IMAGE ]]; then 74 | if [[ "$FORMAT" != 'qcow2' ]]; then 75 | echo "qcow2 format assumed for backing images, but $FORMAT format was supplied." 76 | exit 1 77 | fi 78 | output=$(virsh vol-create-as --pool "$POOL" --name "$NAME" --capacity "$CAPACITY" --format "$FORMAT" --backing-vol "$BACKING_IMAGE" --backing-vol-format "$FORMAT" 2>&1) 79 | result=$? 80 | else 81 | output=$(virsh vol-create-as --pool "$POOL" --name "$NAME" --capacity "$CAPACITY" --format "$FORMAT" 2>&1) 82 | result=$? 83 | fi 84 | if [[ $result -ne 0 ]]; then 85 | echo "Failed to create volume" 86 | echo "$output" 87 | exit $result 88 | fi 89 | 90 | # Determine the path to the volume file. 91 | output=$(virsh vol-key --pool "$POOL" --vol "$NAME" 2>&1) 92 | result=$? 93 | if [[ $result -ne 0 ]]; then 94 | echo "Failed to get volume file path" 95 | echo "$output" 96 | virsh vol-delete --pool "$POOL" --vol "$NAME" 97 | exit $result 98 | fi 99 | 100 | # Change the ownership of the volume to VOLUME_OWNER:VOLUME_GROUP if 101 | # these environmental variables are defined. Without doing this libvirt 102 | # cannot access the volume on RedHat based GNU/Linux distributions. 103 | # Avoid attempting to change permissions on volumes that are not file or 104 | # directory based 105 | if [[ -f "$output" || -d "$output" ]]; then 106 | existing_owner="$(stat --format '%U' "$output")" 107 | existing_group="$(stat --format '%G' "$output")" 108 | new_owner="${VOLUME_OWNER:-$existing_owner}" 109 | new_group="${VOLUME_GROUP:-$existing_group}" 110 | output=$(chown "$new_owner":"$new_group" "$output" 2>&1) 111 | result=$? 112 | if [[ $result -ne 0 ]]; then 113 | echo "Failed to change ownership of the volume to $new_owner:$new_group" 114 | echo "$output" 115 | virsh vol-delete --pool "$POOL" --vol "$NAME" 116 | exit $result 117 | fi 118 | fi 119 | 120 | if [[ -n $IMAGE ]]; then 121 | # Upload an image to the volume. 122 | output=$(virsh vol-upload --pool "$POOL" --vol "$NAME" --file "$IMAGE" 2>&1) 123 | result=$? 124 | if [[ $result -ne 0 ]]; then 125 | echo "Failed to upload image $IMAGE to volume $NAME" 126 | echo "$output" 127 | virsh vol-delete --pool "$POOL" --vol "$NAME" 128 | exit $result 129 | fi 130 | 131 | # Resize the volume to the requested capacity. Attempting to resize a raw volume 132 | # to the same capacity will result in failure, see: 133 | # https://github.com/stackhpc/ansible-role-libvirt-vm/issues/23 134 | if [ "${FORMAT,,}" != "raw" ]; then 135 | output=$(virsh vol-resize --pool "$POOL" --vol "$NAME" --capacity "$CAPACITY" 2>&1) 136 | result=$? 137 | if [[ $result -ne 0 ]]; then 138 | echo "Failed to resize volume $VOLUME to $CAPACITY" 139 | echo "$output" 140 | virsh vol-delete --pool "$POOL" --vol "$NAME" 141 | exit $result 142 | fi 143 | fi 144 | fi 145 | 146 | echo '{"changed": true}' 147 | exit 0 148 | -------------------------------------------------------------------------------- /templates/vm.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ vm.name }} 3 | {% if vm.uuid is defined %} 4 | {{ vm.uuid }} 5 | {% elif (libvirt_vm_default_uuid_deterministic | bool) or (vm.uuid_deterministic is defined and (vm.uuid_deterministic | bool)) %} 6 | {{ vm.name | to_uuid }} 7 | {% endif %} 8 | {{ vm.memory_mb | int * 1024 }} 9 | {{ vm.vcpus }} 10 | {% if vm.clock_offset |default( libvirt_vm_clock_offset ) %} 11 | 12 | {% else %} 13 | 14 | {% endif %} 15 | destroy 16 | restart 17 | destroy 18 | 19 | hvm 20 | 21 | 22 | 23 | 24 | 25 | {% if boot_firmware == "efi" %} 26 | {# NOTE: pflash requires qemu 1.6 or newer. There are alternatives for older versions, but 27 | they do not work with secure boot. See OVMF readme for an overview #} 28 | {{ libvirt_vm_ovmf_efi_firmware_path }} 29 | 30 | {% endif %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% if cpu_mode %} 38 | 39 | 40 | 41 | {% endif %} 42 | 43 | {{ libvirt_vm_emulator }} 44 | {% for volume in volumes %} 45 | 46 | 47 | {% if volume.type | default(libvirt_volume_default_type) == 'file' %} 48 | 49 | {% elif volume.type | default(libvirt_volume_default_type) == 'network' %} 50 | {% if volume.auth.username is defined %} 51 | 52 | 53 | 54 | {% endif %} {# End volume.auth.username check #} 55 | {% if volume.source.name is defined %} 56 | 57 | {% for host in volume.source.hosts_list %} 58 | 59 | {% endfor %} 60 | 61 | {% endif %} {# End volume.source.name check #} 62 | {% elif volume.type | default(libvirt_volume_default_type) == 'block' %} 63 | 64 | {% else %} {# End elif volume.type is defined #} 65 | 66 | {% endif %} 67 | {% if volume.target is undefined %} 68 | 69 | {% else %} 70 | 71 | {% endif %} 72 | 73 | {% endfor %} 74 | {% for interface in interfaces %} 75 | {% if interface.type is defined and interface.type == 'direct' %} 76 | 77 | 78 | {% elif interface.type is defined and interface.type == 'bridge' %} 79 | 80 | 81 | {% elif interface.type is not defined or interface.type == 'network' %} 82 | 83 | 84 | {% endif %} 85 | {% if interface.mac is defined %} 86 | 87 | {% endif %} 88 | {# if the network configuration is invalid this can still appear in the xml #} 89 | {# (say you enter 'bond' instead of 'bridge' in your variables) #} 90 | {% if interface.model is defined %} 91 | 92 | {% else %} 93 | 94 | {% endif %} 95 | {% if interface.alias is defined %} 96 | 97 | {% endif %} 98 | 99 | {% endfor %} 100 | {% if console_log_enabled | bool %} 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {% else %} 110 | 111 | 112 | 113 | 114 | 115 | 116 | {% endif %} 117 | {% if enable_vnc |bool %} 118 | 119 | 120 | 121 | {% endif %} 122 | {% if enable_spice |bool %} 123 | 124 | 125 | 126 | {% endif %} 127 | {% if enable_guest_virtio |bool %} 128 | 129 | 130 | 131 | {% endif %} 132 | {% for usb_device in usb_devices %} 133 | 134 | 135 | 136 | 137 | 138 | 139 | {% endfor %} 140 | /dev/urandom 141 | 142 | 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Libvirt VM 2 | ========== 3 | 4 | This role configures and creates (or destroys) VMs on a KVM hypervisor. 5 | 6 | Requirements 7 | ------------ 8 | 9 | The host should have Virtualization Technology (VT) enabled and should 10 | be preconfigured with libvirt/KVM. 11 | 12 | Role Variables 13 | -------------- 14 | 15 | - `libvirt_vm_default_console_log_dir`: The default directory in which to store 16 | VM console logs, if a VM-specific log file path is not given. Default is 17 | "/var/log/libvirt-consoles". 18 | 19 | - `libvirt_vm_default_uuid_deterministic`: Whether UUID should be calculated by 20 | hashing the VM name. If not, the UUID is randomly generated by libvirt when 21 | the VM is defined. Default is False. 22 | 23 | - `libvirt_vm_image_cache_path`: The directory in which to cache downloaded 24 | images. Default is "/tmp/". 25 | 26 | - `libvirt_volume_default_images_path`: Directory in which instance images are 27 | stored. Default is '/var/lib/libvirt/images'. 28 | 29 | - `libvirt_volume_default_type`: What type of backing volume does the instance 30 | use? Default is `volume`. Options include `block`, `file`, `network` and 31 | `volume`. 32 | 33 | - `libvirt_volume_default_format`: Format for volumes created by the role. 34 | Default is `qcow2`. Options include `raw`, `qcow2`, `vmdk`. See `man virsh` 35 | for the full range. 36 | 37 | - `libvirt_volume_default_device`: Control how device appears in guest OS. 38 | Defaults to `disk`. Options include `cdrom` and `disk`. 39 | 40 | - `libvirt_vm_engine`: virtualisation engine. If not set, the role will attempt 41 | to auto-detect the optimal engine to use. 42 | 43 | - `libvirt_vm_emulator`: path to emulator binary. If not set, the role will 44 | attempt to auto-detect the correct emulator to use. 45 | 46 | - `libvirt_cpu_mode_default`: The default CPU mode if `libvirt_cpu_mode` or 47 | `vm.cpu_mode` is undefined. 48 | 49 | - `libvirt_vm_arch`: CPU architecture, default is `x86_64`. 50 | 51 | - `libvirt_vm_uri`: Override the libvirt connection URI. See the 52 | [libvirt docs](https://libvirt.org/remote.html) docs for more details. 53 | 54 | - `libvirt_vm_virsh_default_env`: Variables contained within this dictionary are 55 | added to the environment used when executing virsh commands. 56 | 57 | - `libvirt_vm_clock_offset`. If defined the instances clock offset is set to 58 | the provided value. When undefined sync is set to `localtime`. 59 | 60 | - `libvirt_vm_trust_guest_rx_filters`: Whether to trust guest receive filters. 61 | This gets mapped to the `trustGuestRxFilters` attribute of VM interfaces. 62 | Default is `false` 63 | 64 | - `libvirt_vms`: list of VMs to be created/destroyed. Each one may have the 65 | following attributes: 66 | 67 | - `state`: set to `present` to create or `absent` to destroy the VM. 68 | Defaults to `present`. 69 | 70 | - `name`: the name to assign to the VM. 71 | 72 | - `uuid`: the UUID to manually assign to the VM. If specified, neither 73 | `uuid_deterministic` nor `libvirt_vm_default_uuid_deterministic` are used. 74 | 75 | - `uuid_deterministic`: overrides default set in 76 | `libvirt_vm_default_uuid_deterministic` 77 | 78 | - `memory_mb`: the memory to assign to the VM, in megabytes. 79 | 80 | - `vcpus`: the number of VCPU cores to assign to the VM. 81 | 82 | - `machine`: Virtual machine type. Default is `None` if 83 | `libvirt_vm_engine` is `kvm`, otherwise `pc-1.0`. 84 | 85 | - `cpu_mode`: Virtual machine CPU mode. Default is `host-passthrough` if 86 | `libvirt_vm_engine` is `kvm`, otherwise `host-model`. Can be set to none 87 | to not configure a cpu mode. 88 | 89 | - `clock_offset`: Overrides default set in `libvirt_vm_clock_offset` 90 | 91 | - `enable_vnc`: If true enables VNC listening on localhost for use with 92 | VirtManager and similar tools 93 | 94 | - `enable_spice`: If true enables SPICE listening for use with 95 | Virtual Machine Manager and similar tools 96 | 97 | - `enable_guest_virtio`: If true enables guest virtio device for use with 98 | Qemu guest agent 99 | 100 | - `volumes`: a list of volumes to attach to the VM. Each volume is 101 | defined with the following dict: 102 | - `type`: What type of backing volume does the instance use? All 103 | options for `libvirt_volume_default_type` are valid here. Default 104 | is `libvirt_volume_default_type`. 105 | - `pool`: Name or UUID of the storage pool from which the volume should be 106 | allocated. Required when `type` is `volume`. 107 | - `name`: Name to associate with the volume being created; For `file` type volumes include extension if you would like volumes created with one. 108 | - `file_path`: Where the image of `file` type volumes should be placed; defaults to `libvirt_volume_default_images_path` 109 | - `device`: Control how device appears in guest OS. All options for 110 | `libvirt_volume_default_device` are valid here. Default is 111 | `libvirt_volume_default_type`. 112 | - `capacity`: volume capacity, can be suffixed with k, M, G, T, P or E when type is `network` or MB,GB,TB, etc when type is `disk` (required when type is `disk` or `network`) 113 | - `auth`: Authentication details should they be required. If auth is required, `username`, `type`, and `uuid` or `usage` will need to be supplied. `uuid` and `usage` should not be both supplied. 114 | - `source`: Where the remote volume comes from when type is `network`. `protocol`, `name` and `hosts_list` should be supplied. `port` is optional. 115 | - `format`: Format of the volume. All options for 116 | `libvirt_volume_default_format` are valid here. Default is 117 | `libvirt_volume_default_format`. 118 | - `image`: (optional) a URL to an image with which the volume is initalised (full copy). 119 | - `checksum`: (optional) checksum of the `image` to avoid download when it's not necessary. 120 | - `backing_image`: (optional) name of the backing volume which is assumed to already be the same pool (copy-on-write). 121 | - `image` and `backing_image` are mutually exclusive options. 122 | - `target`: (optional) Manually influence type and order of volumes 123 | - `dev`: (optional) Block device path when type is `block`. 124 | - `remote_src`: (optional) When type is `file` or `block`, specify wether `image` points to a remote file (true) or a file local to the host that launched the playbook (false). Defaults to true. 125 | 126 | - `usb_devices`: a list of usb devices to present to the vm from the host. 127 | 128 | Each usb device is defined with the following dict: 129 | 130 | - `vendor`: The vendor id of the USB device. 131 | - `product`: The product id of the USB device. 132 | 133 | Note - Libvirt will error if the VM is provisioned and the USB device is not attached. 134 | 135 | To obtain the vendor id and product id of the usb device from the host running as sudo / root with the usb device plugged in 136 | run `lsusb -v`. Example below with an attached Sandisk USB Memory Stick with vendor id: `0x0781` and product id: `0x5567` 137 | 138 | ``` 139 | lsusb -v | grep -A4 -i sandisk 140 | 141 | idVendor 0x0781 SanDisk Corp. 142 | idProduct 0x5567 Cruzer Blade 143 | bcdDevice 1.00 144 | iManufacturer 1 145 | iProduct 2 146 | ``` 147 | 148 | - `interfaces`: a list of network interfaces to attach to the VM. 149 | Each network interface is defined with the following dict: 150 | 151 | - `type`: The type of the interface. Possible values: 152 | 153 | - `network`: Attaches the interface to a named Libvirt virtual 154 | network. This is the default value. 155 | - `direct`: Directly attaches the interface to one of the host's 156 | physical interfaces, using the `macvtap` driver. 157 | - `network`: Name of the network to which an interface should be 158 | attached. Must be specified if and only if the interface `type` is 159 | `network`. 160 | - `mac`: "Hardware" address of the virtual instance, if absent one is created 161 | - `source`: A dict defining the host interface to which this 162 | VM interface should be attached. Must be specified if and only if the 163 | interface `type` is `direct`. Includes the following attributes: 164 | 165 | - `dev`: The name of the host interface to which this VM interface 166 | should be attached. 167 | - `mode`: options include `vepa`, `bridge`, `private` and 168 | `passthrough`. See `man virsh` for more details. Default is 169 | `vepa`. 170 | - `trust_guest_rx_filters`: Whether to trust guest receive filters. 171 | This gets mapped to the `trustGuestRxFilters` attribute of VM 172 | interfaces. Default is `libvirt_vm_trust_guest_rx_filters`. 173 | - `model`: The name of the interface model. Eg. `e1000` or `ne2k_pci`, if undefined 174 | it defaults to `virtio`. 175 | - `alias`: An optional interface alias. This can be used to tie specific network 176 | configuration to persistent network devices via name. The user defined alias is 177 | always prefixed with `ua-` to be compliant (aliases without `ua-` are ignored by libvirt. 178 | If undefined it defaults to libvirt managed `vnetX`. 179 | - `console_log_enabled`: if `true`, log console output to a file at the 180 | path specified by `console_log_path`, **instead of** to a PTY. If 181 | `false`, direct terminal output to a PTY at serial port 0. Default is 182 | `false`. 183 | 184 | - `console_log_path`: Path to console log file. Default is 185 | `{{ libvirt_vm_default_console_log_dir }}/{{ name }}-console.log`. 186 | 187 | - `start`: Whether to immediately start the VM after defining it. Default 188 | is `true`. 189 | 190 | - `autostart`: Whether to start the VM when the host starts up. Default is 191 | `true`. 192 | 193 | - `boot_firmware`: Can be one of: `bios`, or `efi`. Defaults to `bios`. 194 | 195 | - `xml_file`: Optionally supply a modified XML template. Base customisation 196 | off the default `vm.xml.j2` template so as to include the expected jinja 197 | expressions the role uses. 198 | 199 | N.B. the following variables are deprecated: `libvirt_vm_state`, 200 | `libvirt_vm_name`, `libvirt_vm_memory_mb`, `libvirt_vm_vcpus`, 201 | `libvirt_vm_engine`, `libvirt_vm_machine`, `libvirt_vm_cpu_mode`, 202 | `libvirt_vm_volumes`, `libvirt_vm_interfaces` and 203 | `libvirt_vm_console_log_path`. If the variable `libvirt_vms` is left unset, its 204 | default value will be a singleton list containing a VM specification using 205 | these deprecated variables. 206 | 207 | Dependencies 208 | ------------ 209 | 210 | If using qcow2 format drives qemu-img (in qemu-utils package) is required. 211 | 212 | 213 | Example Playbook 214 | ---------------- 215 | 216 | --- 217 | - name: Create VMs 218 | hosts: hypervisor 219 | roles: 220 | - role: stackhpc.libvirt-vm 221 | libvirt_vms: 222 | - state: present 223 | name: 'vm1' 224 | memory_mb: 512 225 | vcpus: 2 226 | volumes: 227 | - name: 'data1' 228 | device: 'disk' 229 | format: 'qcow2' 230 | capacity: '400GB' 231 | pool: 'my-pool' 232 | - name: 'debian-10.2.0-amd64-netinst.iso' 233 | type: 'file' 234 | device: 'cdrom' 235 | format: 'raw' 236 | target: 'hda' # first device on ide bus 237 | - name: 'networkfs' 238 | type: 'network' 239 | format: 'raw' 240 | capacity: '50G' 241 | auth: 242 | username: 'admin' 243 | type: 'ceph' 244 | usage: 'rbd-pool' 245 | source: 246 | protocol: 'rbd' 247 | name: 'rbd/volume' 248 | hosts_list: 249 | - 'mon1.example.org' 250 | - 'mon2.example.org' 251 | - 'mon3.example.org' 252 | - type: 'block' 253 | format: 'raw' 254 | dev: '/dev/sda' 255 | 256 | interfaces: 257 | - network: 'br-datacentre' 258 | 259 | usb_devices: 260 | - vendor: '0x0781' 261 | product: '0x5567' 262 | 263 | - state: present 264 | name: 'vm2' 265 | memory_mb: 1024 266 | vcpus: 1 267 | volumes: 268 | - name: 'data2' 269 | device: 'disk' 270 | format: 'qcow2' 271 | capacity: '200GB' 272 | pool: 'my-pool' 273 | - name: 'filestore' 274 | type: 'file' 275 | file_path: '/srv/cloud/images' 276 | capacity: '900GB' 277 | interfaces: 278 | - type: 'direct' 279 | source: 280 | dev: 'eth123' 281 | mode: 'private' 282 | - type: 'bridge' 283 | source: 284 | dev: 'br-datacentre' 285 | 286 | 287 | Author Information 288 | ------------------ 289 | 290 | - Mark Goddard () 291 | --------------------------------------------------------------------------------