Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Please login to access the resource
SUSE:SLE-12-SP2:Update
salt.22692
opensuse-3000.2-virt-backports-236.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File opensuse-3000.2-virt-backports-236.patch of Package salt.22692
From da64fe05978a9d0479d6d75f480f19d8d9ef0a1f Mon Sep 17 00:00:00 2001 From: Cedric Bosdonnat <cbosdonnat@suse.com> Date: Wed, 10 Jun 2020 14:24:49 +0200 Subject: [PATCH] Opensuse 3000.2 virt backports (#236) * Revert "virt._get_domain: don't raise an exception if there is no VM" This reverts commit 9d433776fc252ab872b331cfa7fc940c42452f83. * Revert "openSUSE-3000 virt-defined-states (#222)" This reverts commit 900fdc4d5bcfdac933f9a411d38b92ef47dd1ef5. * Revert "Add virt.all_capabilities" This reverts commit 96e8307b531f7e51f5587f66a51dc52ad246c8c1. * Blacken salt -- virt only * virt._get_domain: don't raise an exception if there is no VM Raising an exception if there is no VM in _get_domain makes sense if looking for some VMs, but not when listing all VMs. * fixed bug on update existing boot parameters * add support to boot vm with UEFI * virt: don't depend on ElementTree.tostring to compare XML fragments ElementTree.tostring() implementation starts to keep the attribute order defined by the user in python 3.8. This can lead to equal elements to be considered different. Use xmlutil.to_dict(element, True) to compare the elements. This also uncovered a wrong behavior of pool_update when only changing the password. * openSUSE-3000 virt-defined-states (#222) * Create virt.pool_defined state out of virt.pool_running Users may want to use states to ensure a virtual storage pool is defined and not enforce it to be running. Extract the code that performs the pool definition / update from virt.pool_running state into a virt.pool_defined. Obviously the virt.pool_running state calls the virt.pool_defined one. In such a case no additionnal test is needed for virt.pool_defined since this is already tested with virt.pool_running. * Add virt.update test parameter In order to allow running dry-runs of virt.update module add a test parameter. This will later be used by the virt states. * Extract virt.defined state from virt.running In order to ensure a virtual guest is defined independently of its status, extract the corresponding code from the virt.running state. This commit also handles the __opts__['test'] for the running state. Since the update call only performs changes if needed, deprecate the update parameter. * Extract virt.network_defined from virt.network_running Just like domains and storage pools, users may want to ensure a network is defined without influencing it's status. Extract the code from network_running state defining the network into a network_defined state. While at it, support __opt__['test'] == True in these states. Updating the network definition in the pool_defined state will come in a future PR. * Fix virt.update to handle None mem and cpu virt.running state now may call virt.update with None mem and cpu parameters. This was not handled in _gen_xml(). Also add some more tests cases matching this for virt.update. * Add support to virt for libvirt loader * Add bhyve compatibility to virt * Fix test_get_hypervisor: mock bhyve * Add virt.all_capabilities In order to get all possible capabilities from a host, the user has to call virt.capabilities, and then loop over the guests and domains before calling virt.domain_capabilities for each of them. This commit embeds all this logic to get them all in a single virt.all_capabilities call. * Virt init disks support (PR#56666) * Fix pylint warning in test_virt.py after blackening * Add pool parameter to virt.define_vol_xml_str * Remove useless default values for disks and vm_name in _disk_profile * virt._gen_vol_xml: move all esx-specifics outside In the near future gen_vol_xml will be able to handle many volume types, not only for ESX volumes. For this, clean up the function from all the ESX-specifics code and move them to the caller code. The volume key and target path values have also been removed since those are read-only elements that should not be provided for volume creation as per https://libvirt.org/formatstorage.html#StorageVol * virt: add more properties to generate volume XML In order to generate almost all the volumes that libvirt can handle, add the type, backingStore, permissions and allocation parameters to the virt._gen_vol_xml() function. Also make the format parameter optional since libvirt has default values depending on the storage backend. * virt.define_vol_xml_str variant using an existing libvirt connection In order to avoid connection multiple times when reusing this function in the virt module, create _define_vol_xml_str not caring about the connection opening and closing. * Add virt.volume_define function In the same vein than pool_define and network_define, expose a volume_define function to let users create a volume without needing to know all of libvirt's XML details. * Add virt.volume_upload function When using volumes the user can just copy the template disk image into the target path. In such a case, the image needs to be uploaded into the volume. * virt.capabilities refactoring Extract the libvirt-handling code from virt.capabilities into a virt._capabilities function accepting an opened libvirt connection. This allows reusing the code in other functions with easy connection handling. * Extract virt.pool_capabilities logic for use with a libvirt connection Te virt.pool_capabilities function computes a lot of interesting values on the virtual storage pool types. Extract the logic of it into virt._pool_capabilities for reuse. * Share libvirt connection in virt.init Since the next commits will introduce more uses of the libvirt connection in virt.init(), start sharing it now. * Add disk volumes support in virt.init In order to support creating VMs with disk on more storage pools like iSCSI, disks, LVM, RBD, etc, virt.init needs to handle disks not only as files, but also as libvirt volumes. * fix libvirtError use libvirtError is not defined, libvirt.libvirtError should be used instead. * virt: let libvirt generate MAC addresses There is no need to generate MAC addresses in the virt module if the user hasn't provided any. This only makes it harder to make the difference between a real mac address change from the user and a new generated one. Now the mac address is not written in the domain XML definition if not provided by the user. This avoids unnecessary changes when applying virt.running. * virt.update handle disk volumes * virt.volume_infos: output backing store as well Since it could be useful to know whether a volume has a backing store, output the path and format of the backing store if any is defined. * virt.volume_infos: output disk format Since the format of a volume may be of interest and could help to tell if two volumes are similar, output this information in the virt.volume_infos function. * Add virt.volume_defined state In order to help creating storage volumes in virtual storage pools from states, add a virt.volume_defined state. * Add volume support to virt._get_disks If a virtual machine has disks of volume types, they will now be reported by the virt._get_disk() function and all the user-exposed functions using it like virt.get_disks(), virt.vm_info() and virt.full_info(). * Add volume disks support to virt.purge() virt.purge will now remove not only the file disks, but also the disk volumes. * Handle RBD volumes as network disks in VM definition libvirt doesn't support attaching RBD storage pool volumes to a VM. For instance, a disk with such a source is invalid: <source pool='rbd-pool' volume='rbd-volume'/> And needs to be replaced with: <source protocol="rbd" name="rbd-pool-name/rbd-volume"> <host name="hostname" port="7000"/> <auth username='myuser'> <secret type='ceph' usage='mypassid'/> </auth> </source> This means that we need to fetch the connection data from the pool definition and convert the volume disk definition into a network one when creating the VM. This also means that when purging the VM we need to search for the pool by looking in every pool's XML to delete the volume. * virt: fix VM creating with disk volume Since volumes in a virtual storage pool of type 'disk' are partitions, then need to be named after the disk name with sequential numbers rather than using the VM and disk names. Also, the format passed by the user is the one used when creating the volume. However in the VM definition for logical and disk volumes, the format should be set to raw. * virt: handle cdrom remote images Libvirt allows to use network images for cdroms. Use them if the image is a remote URL for a cdrom device. * virt.update properly handle removable devices Live attaching / detaching removable devices results in failures. To change the source of those, we need to call updateDeviceFlags instead. * improve boot parameter documentation (PR#57086) * Remove buggy start parameter from virt.pool_running docstring As mentioned by issue #57275, the start parameter in virt.pool_running documentation is not implemented at all. Remove it from the doc. * virt: fix removable_changes definition place * virt: show the proper pool name in get_disks From the user point of view the internal RBD pool name doesn't make sense when listing the disks of a VM. We need then to resolve it to the libvirt pool name if possible. Move the corresponding code from purge to get_disks. * virt: fix updating VM with backing store disks libvirt adds the index attribute in the XML definition when there is a backing store file for a disk. We need to elude it before comparing the sources to avoid trying to recreate disks in such cases. * virt: default cdrom model to ide cdrom devices can't use virtio. Set the default bus to ide for cdroms. * virt.get_disk: output the full URL for cdroms with remote image virt._gen_xml converts the cdroms with remote URL into a network device, but to be coherent with the user input the virt.get_disk() function needs to reassemble the URL in thoses cases. * virt.pool_delete: remove also secret Some pool type require a secret for the authentication on the remote source. Remove the secrets that were added by pool_defined but leave the others in place. * virt.init: cdrom format default to raw cdrom sources can't be of format qcow2. Force raw as the default if needed when creating VM with source_file set for the cdrom. * virt.init: fix disk target names Fixes issue #57477. * virt.update: handle changing cdrom images for devices with remote source When a DVD device on a VM has a remote source, virt.update needs to be able to handle detaching it and attaching a file image live. * virt.init: fix the name of volumes reused in disk-types pools Only compute the partition name if no source_file was provided by the user for a pool of disk type. Co-authored-by: Blacken Salt <jenkins@saltstack.com> Co-authored-by: Firefly <leevn2011@hotmail.com> Co-authored-by: Jeroen Schutrup <jeroenschutrup@hotmail.nl> Co-authored-by: ch3ll <megan.wilhite@gmail.com> --- changelog/57005.added | 1 + changelog/57275.fixed | 1 + changelog/57477.fixed | 1 + changelog/57497.fixed | 1 + salt/modules/virt.py | 4756 ++++++++++-------- salt/states/virt.py | 1440 +++--- salt/templates/virt/libvirt_domain.jinja | 29 +- salt/templates/virt/libvirt_volume.jinja | 40 +- tests/unit/modules/test_virt.py | 5564 +++++++++++++++------- tests/unit/states/test_virt.py | 4638 +++++++++++------- 10 files changed, 10558 insertions(+), 5913 deletions(-) create mode 100644 changelog/57005.added create mode 100644 changelog/57275.fixed create mode 100644 changelog/57477.fixed create mode 100644 changelog/57497.fixed diff --git a/changelog/57005.added b/changelog/57005.added new file mode 100644 index 0000000000..6704541509 --- /dev/null +++ b/changelog/57005.added @@ -0,0 +1 @@ +Add support for disks volumes in virt.running state diff --git a/changelog/57275.fixed b/changelog/57275.fixed new file mode 100644 index 0000000000..6efbe48315 --- /dev/null +++ b/changelog/57275.fixed @@ -0,0 +1 @@ +Remove buggy start parameter from virt.pool_running docstring diff --git a/changelog/57477.fixed b/changelog/57477.fixed new file mode 100644 index 0000000000..f32f32fdfc --- /dev/null +++ b/changelog/57477.fixed @@ -0,0 +1 @@ +virt.init fix the disk target names diff --git a/changelog/57497.fixed b/changelog/57497.fixed new file mode 100644 index 0000000000..24697e108d --- /dev/null +++ b/changelog/57497.fixed @@ -0,0 +1 @@ +Fix volume name for disk-typed pools in virt.defined diff --git a/salt/modules/virt.py b/salt/modules/virt.py index c8e046a47a..a78c21e323 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -''' +""" Work with virtual machines managed by libvirt :depends: libvirt Python module @@ -68,7 +68,7 @@ The calls not using the libvirt connection setup are: - `libvirt URI format <http://libvirt.org/uri.html#URI_config>`_ - `libvirt authentication configuration <http://libvirt.org/auth.html#Auth_client_config>`_ -''' +""" # Special Thanks to Michael Dehann, many of the concepts, and a few structures # of his in the virt func module have been used @@ -76,26 +76,21 @@ The calls not using the libvirt connection setup are: from __future__ import absolute_import, print_function, unicode_literals import base64 import copy +import datetime +import logging import os import re -import sys import shutil -import subprocess import string # pylint: disable=deprecated-module -import logging +import subprocess +import sys import time -import datetime from xml.etree import ElementTree +from xml.sax import saxutils # Import third party libs import jinja2 import jinja2.exceptions -try: - import libvirt # pylint: disable=import-error - from libvirt import libvirtError - HAS_LIBVIRT = True -except ImportError: - HAS_LIBVIRT = False # Import salt libs import salt.utils.files @@ -106,49 +101,65 @@ import salt.utils.stringutils import salt.utils.templates import salt.utils.validate.net import salt.utils.versions +import salt.utils.xmlutil as xmlutil import salt.utils.yaml - -from salt.utils.virt import check_remote, download_remote +from salt._compat import ipaddress from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin -from salt._compat import ipaddress +from salt.ext.six.moves.urllib.parse import urlparse, urlunparse +from salt.utils.virt import check_remote, download_remote + +try: + import libvirt # pylint: disable=import-error + + # pylint: disable=no-name-in-module + from libvirt import libvirtError + + # pylint: enable=no-name-in-module + + HAS_LIBVIRT = True +except ImportError: + HAS_LIBVIRT = False + log = logging.getLogger(__name__) # Set up template environment JINJA = jinja2.Environment( loader=jinja2.FileSystemLoader( - os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt') + os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, "virt") ) ) -CACHE_DIR = '/var/lib/libvirt/saltinst' +CACHE_DIR = "/var/lib/libvirt/saltinst" -VIRT_STATE_NAME_MAP = {0: 'running', - 1: 'running', - 2: 'running', - 3: 'paused', - 4: 'shutdown', - 5: 'shutdown', - 6: 'crashed'} +VIRT_STATE_NAME_MAP = { + 0: "running", + 1: "running", + 2: "running", + 3: "paused", + 4: "shutdown", + 5: "shutdown", + 6: "crashed", +} def __virtual__(): if not HAS_LIBVIRT: - return (False, 'Unable to locate or import python libvirt library.') - return 'virt' + return (False, "Unable to locate or import python libvirt library.") + return "virt" def __get_request_auth(username, password): - ''' + """ Get libvirt.openAuth callback with username, password values overriding the configuration ones. - ''' + """ # pylint: disable=unused-argument def __request_auth(credentials, user_data): - '''Callback method passed to libvirt.openAuth(). + """Callback method passed to libvirt.openAuth(). The credentials argument is a list of credentials that libvirt would like to request. An element of this list is a list containing @@ -160,21 +171,31 @@ def __get_request_auth(username, password): - a place to store the actual result for the request The user_data argument is currently not set in the openAuth call. - ''' + """ for credential in credentials: if credential[0] == libvirt.VIR_CRED_AUTHNAME: - credential[4] = username if username else \ - __salt__['config.get']('virt:connection:auth:username', credential[3]) + credential[4] = ( + username + if username + else __salt__["config.get"]( + "virt:connection:auth:username", credential[3] + ) + ) elif credential[0] == libvirt.VIR_CRED_NOECHOPROMPT: - credential[4] = password if password else \ - __salt__['config.get']('virt:connection:auth:password', credential[3]) + credential[4] = ( + password + if password + else __salt__["config.get"]( + "virt:connection:auth:password", credential[3] + ) + ) else: - log.info('Unhandled credential type: %s', credential[0]) + log.info("Unhandled credential type: %s", credential[0]) return 0 def __get_conn(**kwargs): - ''' + """ Detects what type of dom this node is and attempts to connect to the correct hypervisor via libvirt. @@ -182,99 +203,65 @@ def __get_conn(**kwargs): :param username: username to connect with, overriding defaults :param password: password to connect with, overriding defaults - ''' + """ # This has only been tested on kvm and xen, it needs to be expanded to # support all vm layers supported by libvirt + # Connection string works on bhyve, but auth is not tested. - username = kwargs.get('username', None) - password = kwargs.get('password', None) - conn_str = kwargs.get('connection', None) + username = kwargs.get("username", None) + password = kwargs.get("password", None) + conn_str = kwargs.get("connection", None) if not conn_str: - conn_str = __salt__['config.get']('virt.connect', None) - if conn_str is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'virt.connect\' configuration property has been deprecated in favor ' - 'of \'virt:connection:uri\'. \'virt.connect\' will stop being used in ' - '{version}.' - ) - else: - conn_str = __salt__['config.get']('libvirt:connection', None) - if conn_str is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'libvirt.connection\' configuration property has been deprecated in favor ' - 'of \'virt:connection:uri\'. \'libvirt.connection\' will stop being used in ' - '{version}.' - ) - - conn_str = __salt__['config.get']('virt:connection:uri', conn_str) - - hypervisor = __salt__['config.get']('libvirt:hypervisor', None) - if hypervisor is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'libvirt.hypervisor\' configuration property has been deprecated. ' - 'Rather use the \'virt:connection:uri\' to properly define the libvirt ' - 'URI or alias of the host to connect to. \'libvirt:hypervisor\' will ' - 'stop being used in {version}.' - ) - - if hypervisor == 'esxi' and conn_str is None: - salt.utils.versions.warn_until( - 'Sodium', - 'esxi hypervisor default with no default connection URI detected, ' - 'please set \'virt:connection:uri\' to \'esx\' for keep the legacy ' - 'behavior. Will default to libvirt guess once \'libvirt:hypervisor\' ' - 'configuration is removed in {version}.' - ) - conn_str = 'esx' + conn_str = __salt__["config.get"]("virt:connection:uri", conn_str) try: - auth_types = [libvirt.VIR_CRED_AUTHNAME, - libvirt.VIR_CRED_NOECHOPROMPT, - libvirt.VIR_CRED_ECHOPROMPT, - libvirt.VIR_CRED_PASSPHRASE, - libvirt.VIR_CRED_EXTERNAL] - conn = libvirt.openAuth(conn_str, [auth_types, __get_request_auth(username, password), None], 0) + auth_types = [ + libvirt.VIR_CRED_AUTHNAME, + libvirt.VIR_CRED_NOECHOPROMPT, + libvirt.VIR_CRED_ECHOPROMPT, + libvirt.VIR_CRED_PASSPHRASE, + libvirt.VIR_CRED_EXTERNAL, + ] + conn = libvirt.openAuth( + conn_str, [auth_types, __get_request_auth(username, password), None], 0 + ) except Exception: # pylint: disable=broad-except raise CommandExecutionError( - 'Sorry, {0} failed to open a connection to the hypervisor ' - 'software at {1}'.format( - __grains__['fqdn'], - conn_str - ) + "Sorry, {0} failed to open a connection to the hypervisor " + "software at {1}".format(__grains__["fqdn"], conn_str) ) return conn def _get_domain(conn, *vms, **kwargs): - ''' + """ Return a domain object for the named VM or return domain object for all VMs. :params conn: libvirt connection object :param vms: list of domain names to look for :param iterable: True to return an array in all cases - ''' + """ ret = list() lookup_vms = list() all_vms = [] - if kwargs.get('active', True): + if kwargs.get("active", True): for id_ in conn.listDomainsID(): all_vms.append(conn.lookupByID(id_).name()) - if kwargs.get('inactive', True): + if kwargs.get("inactive", True): for id_ in conn.listDefinedDomains(): all_vms.append(id_) if vms and not all_vms: - raise CommandExecutionError('No virtual machines found.') + raise CommandExecutionError("No virtual machines found.") if vms: for name in vms: if name not in all_vms: - raise CommandExecutionError('The VM "{name}" is not present'.format(name=name)) + raise CommandExecutionError( + 'The VM "{name}" is not present'.format(name=name) + ) else: lookup_vms.append(name) else: @@ -283,52 +270,69 @@ def _get_domain(conn, *vms, **kwargs): for name in lookup_vms: ret.append(conn.lookupByName(name)) - return len(ret) == 1 and not kwargs.get('iterable') and ret[0] or ret + return len(ret) == 1 and not kwargs.get("iterable") and ret[0] or ret def _parse_qemu_img_info(info): - ''' + """ Parse qemu-img info JSON output into disk infos dictionary - ''' + """ raw_infos = salt.utils.json.loads(info) disks = [] for disk_infos in raw_infos: disk = { - 'file': disk_infos['filename'], - 'file format': disk_infos['format'], - 'disk size': disk_infos['actual-size'], - 'virtual size': disk_infos['virtual-size'], - 'cluster size': disk_infos['cluster-size'] if 'cluster-size' in disk_infos else None, - } - - if 'full-backing-filename' in disk_infos.keys(): - disk['backing file'] = format(disk_infos['full-backing-filename']) - - if 'snapshots' in disk_infos.keys(): - disk['snapshots'] = [ - { - 'id': snapshot['id'], - 'tag': snapshot['name'], - 'vmsize': snapshot['vm-state-size'], - 'date': datetime.datetime.fromtimestamp( - float('{}.{}'.format(snapshot['date-sec'], snapshot['date-nsec']))).isoformat(), - 'vmclock': datetime.datetime.utcfromtimestamp( - float('{}.{}'.format(snapshot['vm-clock-sec'], - snapshot['vm-clock-nsec']))).time().isoformat() - } for snapshot in disk_infos['snapshots']] + "file": disk_infos["filename"], + "file format": disk_infos["format"], + "disk size": disk_infos["actual-size"], + "virtual size": disk_infos["virtual-size"], + "cluster size": disk_infos["cluster-size"] + if "cluster-size" in disk_infos + else None, + } + + if "full-backing-filename" in disk_infos.keys(): + disk["backing file"] = format(disk_infos["full-backing-filename"]) + + if "snapshots" in disk_infos.keys(): + disk["snapshots"] = [ + { + "id": snapshot["id"], + "tag": snapshot["name"], + "vmsize": snapshot["vm-state-size"], + "date": datetime.datetime.fromtimestamp( + float( + "{}.{}".format(snapshot["date-sec"], snapshot["date-nsec"]) + ) + ).isoformat(), + "vmclock": datetime.datetime.utcfromtimestamp( + float( + "{}.{}".format( + snapshot["vm-clock-sec"], snapshot["vm-clock-nsec"] + ) + ) + ) + .time() + .isoformat(), + } + for snapshot in disk_infos["snapshots"] + ] disks.append(disk) for disk in disks: - if 'backing file' in disk.keys(): - candidates = [info for info in disks if 'file' in info.keys() and info['file'] == disk['backing file']] + if "backing file" in disk.keys(): + candidates = [ + info + for info in disks + if "file" in info.keys() and info["file"] == disk["backing file"] + ] if candidates: - disk['backing file'] = candidates[0] + disk["backing file"] = candidates[0] return disks[0] def _get_uuid(dom): - ''' + """ Return a uuid from the named vm CLI Example: @@ -336,12 +340,12 @@ def _get_uuid(dom): .. code-block:: bash salt '*' virt.get_uuid <domain> - ''' - return ElementTree.fromstring(get_xml(dom)).find('uuid').text + """ + return ElementTree.fromstring(get_xml(dom)).find("uuid").text def _get_on_poweroff(dom): - ''' + """ Return `on_poweroff` setting from the named vm CLI Example: @@ -349,13 +353,13 @@ def _get_on_poweroff(dom): .. code-block:: bash salt '*' virt.get_on_restart <domain> - ''' - node = ElementTree.fromstring(get_xml(dom)).find('on_poweroff') - return node.text if node is not None else '' + """ + node = ElementTree.fromstring(get_xml(dom)).find("on_poweroff") + return node.text if node is not None else "" def _get_on_reboot(dom): - ''' + """ Return `on_reboot` setting from the named vm CLI Example: @@ -363,13 +367,13 @@ def _get_on_reboot(dom): .. code-block:: bash salt '*' virt.get_on_reboot <domain> - ''' - node = ElementTree.fromstring(get_xml(dom)).find('on_reboot') - return node.text if node is not None else '' + """ + node = ElementTree.fromstring(get_xml(dom)).find("on_reboot") + return node.text if node is not None else "" def _get_on_crash(dom): - ''' + """ Return `on_crash` setting from the named vm CLI Example: @@ -377,460 +381,714 @@ def _get_on_crash(dom): .. code-block:: bash salt '*' virt.get_on_crash <domain> - ''' - node = ElementTree.fromstring(get_xml(dom)).find('on_crash') - return node.text if node is not None else '' + """ + node = ElementTree.fromstring(get_xml(dom)).find("on_crash") + return node.text if node is not None else "" def _get_nics(dom): - ''' + """ Get domain network interfaces from a libvirt domain object. - ''' + """ nics = {} doc = ElementTree.fromstring(dom.XMLDesc(0)) - for iface_node in doc.findall('devices/interface'): + for iface_node in doc.findall("devices/interface"): nic = {} - nic['type'] = iface_node.get('type') + nic["type"] = iface_node.get("type") for v_node in iface_node: - if v_node.tag == 'mac': - nic['mac'] = v_node.get('address') - if v_node.tag == 'model': - nic['model'] = v_node.get('type') - if v_node.tag == 'target': - nic['target'] = v_node.get('dev') + if v_node.tag == "mac": + nic["mac"] = v_node.get("address") + if v_node.tag == "model": + nic["model"] = v_node.get("type") + if v_node.tag == "target": + nic["target"] = v_node.get("dev") # driver, source, and match can all have optional attributes - if re.match('(driver|source|address)', v_node.tag): + if re.match("(driver|source|address)", v_node.tag): temp = {} for key, value in six.iteritems(v_node.attrib): temp[key] = value nic[v_node.tag] = temp # virtualport needs to be handled separately, to pick up the # type attribute of the virtualport itself - if v_node.tag == 'virtualport': + if v_node.tag == "virtualport": temp = {} - temp['type'] = v_node.get('type') + temp["type"] = v_node.get("type") for key, value in six.iteritems(v_node.attrib): temp[key] = value - nic['virtualport'] = temp - if 'mac' not in nic: + nic["virtualport"] = temp + if "mac" not in nic: continue - nics[nic['mac']] = nic + nics[nic["mac"]] = nic return nics def _get_graphics(dom): - ''' + """ Get domain graphics from a libvirt domain object. - ''' - out = {'autoport': 'None', - 'keymap': 'None', - 'listen': 'None', - 'port': 'None', - 'type': 'None'} + """ + out = { + "autoport": "None", + "keymap": "None", + "listen": "None", + "port": "None", + "type": "None", + } doc = ElementTree.fromstring(dom.XMLDesc(0)) - for g_node in doc.findall('devices/graphics'): + for g_node in doc.findall("devices/graphics"): for key, value in six.iteritems(g_node.attrib): out[key] = value return out -def _get_disks(dom): - ''' +def _get_loader(dom): + """ + Get domain loader from a libvirt domain object. + """ + out = {"path": "None"} + doc = ElementTree.fromstring(dom.XMLDesc(0)) + for g_node in doc.findall("os/loader"): + out["path"] = g_node.text + for key, value in six.iteritems(g_node.attrib): + out[key] = value + return out + + +def _get_disks(conn, dom): + """ Get domain disks from a libvirt domain object. - ''' + """ disks = {} doc = ElementTree.fromstring(dom.XMLDesc(0)) - for elem in doc.findall('devices/disk'): - source = elem.find('source') + for elem in doc.findall("devices/disk"): + source = elem.find("source") if source is None: continue - target = elem.find('target') + target = elem.find("target") + driver = elem.find("driver") if target is None: continue - if 'dev' in target.attrib: - qemu_target = source.get('file', '') - if not qemu_target: - qemu_target = source.get('dev', '') - if not qemu_target and 'protocol' in source.attrib and 'name' in source.attrib: # for rbd network - qemu_target = '{0}:{1}'.format( - source.get('protocol'), - source.get('name')) + qemu_target = None + extra_properties = None + if "dev" in target.attrib: + disk_type = elem.get("type") + if disk_type == "file": + qemu_target = source.get("file", "") + if qemu_target.startswith("/dev/zvol/"): + disks[target.get("dev")] = {"file": qemu_target, "zfs": True} + continue + # Extract disk sizes, snapshots, backing files + if elem.get("device", "disk") != "cdrom": + try: + stdout = subprocess.Popen( + [ + "qemu-img", + "info", + "-U", + "--output", + "json", + "--backing-chain", + qemu_target, + ], + shell=False, + stdout=subprocess.PIPE, + ).communicate()[0] + qemu_output = salt.utils.stringutils.to_str(stdout) + output = _parse_qemu_img_info(qemu_output) + extra_properties = output + except TypeError: + disk.update({"file": "Does not exist"}) + elif disk_type == "block": + qemu_target = source.get("dev", "") + elif disk_type == "network": + qemu_target = source.get("protocol") + source_name = source.get("name") + if source_name: + qemu_target = "{0}:{1}".format(qemu_target, source_name) + + # Reverse the magic for the rbd and gluster pools + if source.get("protocol") in ["rbd", "gluster"]: + for pool_i in conn.listAllStoragePools(): + pool_i_xml = ElementTree.fromstring(pool_i.XMLDesc()) + name_node = pool_i_xml.find("source/name") + if name_node is not None and source_name.startswith( + "{}/".format(name_node.text) + ): + qemu_target = "{}{}".format( + pool_i.name(), source_name[len(name_node.text) :] + ) + break + + # Reverse the magic for cdroms with remote URLs + if elem.get("device", "disk") == "cdrom": + host_node = source.find("host") + if host_node is not None: + hostname = host_node.get("name") + port = host_node.get("port") + qemu_target = urlunparse( + ( + source.get("protocol"), + "{}:{}".format(hostname, port) if port else hostname, + source_name, + "", + saxutils.unescape(source.get("query", "")), + "", + ) + ) + elif disk_type == "volume": + pool_name = source.get("pool") + volume_name = source.get("volume") + qemu_target = "{}/{}".format(pool_name, volume_name) + pool = conn.storagePoolLookupByName(pool_name) + vol = pool.storageVolLookupByName(volume_name) + vol_info = vol.info() + extra_properties = { + "virtual size": vol_info[1], + "disk size": vol_info[2], + } + + backing_files = [ + { + "file": node.find("source").get("file"), + "file format": node.find("format").get("type"), + } + for node in elem.findall(".//backingStore[source]") + ] + + if backing_files: + # We had the backing files in a flat list, nest them again. + extra_properties["backing file"] = backing_files[0] + parent = extra_properties["backing file"] + for sub_backing_file in backing_files[1:]: + parent["backing file"] = sub_backing_file + parent = sub_backing_file + + else: + # In some cases the backing chain is not displayed by the domain definition + # Try to see if we have some of it in the volume definition. + vol_desc = ElementTree.fromstring(vol.XMLDesc()) + backing_path = vol_desc.find("./backingStore/path") + backing_format = vol_desc.find("./backingStore/format") + if backing_path is not None: + extra_properties["backing file"] = {"file": backing_path.text} + if backing_format is not None: + extra_properties["backing file"][ + "file format" + ] = backing_format.get("type") + if not qemu_target: continue - disk = {'file': qemu_target, 'type': elem.get('device')} - - driver = elem.find('driver') - if driver is not None and driver.get('type') == 'qcow2': - try: - stdout = subprocess.Popen( - ['qemu-img', 'info', '-U', '--output', 'json', '--backing-chain', disk['file']], - shell=False, - stdout=subprocess.PIPE).communicate()[0] - qemu_output = salt.utils.stringutils.to_str(stdout) - output = _parse_qemu_img_info(qemu_output) - disk.update(output) - except TypeError: - disk.update({'file': 'Does not exist'}) - - disks[target.get('dev')] = disk + disk = { + "file": qemu_target, + "type": elem.get("device"), + } + if driver is not None and "type" in driver.attrib: + disk["file format"] = driver.get("type") + if extra_properties: + disk.update(extra_properties) + + disks[target.get("dev")] = disk return disks def _libvirt_creds(): - ''' + """ Returns the user and group that the disk images should be owned by - ''' - g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf' - u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf' + """ + g_cmd = "grep ^\\s*group /etc/libvirt/qemu.conf" + u_cmd = "grep ^\\s*user /etc/libvirt/qemu.conf" try: - stdout = subprocess.Popen(g_cmd, - shell=True, - stdout=subprocess.PIPE).communicate()[0] + stdout = subprocess.Popen( + g_cmd, shell=True, stdout=subprocess.PIPE + ).communicate()[0] group = salt.utils.stringutils.to_str(stdout).split('"')[1] except IndexError: - group = 'root' + group = "root" try: - stdout = subprocess.Popen(u_cmd, - shell=True, - stdout=subprocess.PIPE).communicate()[0] + stdout = subprocess.Popen( + u_cmd, shell=True, stdout=subprocess.PIPE + ).communicate()[0] user = salt.utils.stringutils.to_str(stdout).split('"')[1] except IndexError: - user = 'root' - return {'user': user, 'group': group} + user = "root" + return {"user": user, "group": group} def _get_migrate_command(): - ''' + """ Returns the command shared by the different migration types - ''' - tunnel = __salt__['config.option']('virt.tunnel') - if tunnel: - salt.utils.versions.warn_until( - 'Sodium', - '\'virt.tunnel\' has been deprecated in favor of ' - '\'virt:tunnel\'. \'virt.tunnel\' will stop ' - 'being used in {version}.') - else: - tunnel = __salt__['config.get']('virt:tunnel') + """ + tunnel = __salt__["config.get"]("virt:tunnel") if tunnel: - return ('virsh migrate --p2p --tunnelled --live --persistent ' - '--undefinesource ') - return 'virsh migrate --live --persistent --undefinesource ' + return ( + "virsh migrate --p2p --tunnelled --live --persistent " "--undefinesource " + ) + return "virsh migrate --live --persistent --undefinesource " def _get_target(target, ssh): - ''' + """ Compute libvirt URL for target migration host. - ''' - proto = 'qemu' + """ + proto = "qemu" if ssh: - proto += '+ssh' - return ' {0}://{1}/{2}'.format(proto, target, 'system') - - -def _gen_xml(name, - cpu, - mem, - diskp, - nicp, - hypervisor, - os_type, - arch, - graphics=None, - boot=None, - **kwargs): - ''' + proto += "+ssh" + return " {0}://{1}/{2}".format(proto, target, "system") + + +def _gen_xml( + conn, + name, + cpu, + mem, + diskp, + nicp, + hypervisor, + os_type, + arch, + graphics=None, + boot=None, + **kwargs +): + """ Generate the XML string to define a libvirt VM - ''' + """ mem = int(mem) * 1024 # MB context = { - 'hypervisor': hypervisor, - 'name': name, - 'cpu': six.text_type(cpu), - 'mem': six.text_type(mem), + "hypervisor": hypervisor, + "name": name, + "cpu": six.text_type(cpu), + "mem": six.text_type(mem), } - if hypervisor in ['qemu', 'kvm']: - context['controller_model'] = False - elif hypervisor == 'vmware': + if hypervisor in ["qemu", "kvm"]: + context["controller_model"] = False + elif hypervisor == "vmware": # TODO: make bus and model parameterized, this works for 64-bit Linux - context['controller_model'] = 'lsilogic' + context["controller_model"] = "lsilogic" # By default, set the graphics to listen to all addresses if graphics: - if 'listen' not in graphics: - graphics['listen'] = {'type': 'address', 'address': '0.0.0.0'} - elif 'address' not in graphics['listen'] and graphics['listen']['type'] == 'address': - graphics['listen']['address'] = '0.0.0.0' + if "listen" not in graphics: + graphics["listen"] = {"type": "address", "address": "0.0.0.0"} + elif ( + "address" not in graphics["listen"] + and graphics["listen"]["type"] == "address" + ): + graphics["listen"]["address"] = "0.0.0.0" # Graphics of type 'none' means no graphics device at all - if graphics.get('type', 'none') == 'none': + if graphics.get("type", "none") == "none": graphics = None - context['graphics'] = graphics + context["graphics"] = graphics - if 'boot_dev' in kwargs: - context['boot_dev'] = [] - for dev in kwargs['boot_dev'].split(): - context['boot_dev'].append(dev) + if "boot_dev" in kwargs: + context["boot_dev"] = [] + for dev in kwargs["boot_dev"].split(): + context["boot_dev"].append(dev) else: - context['boot_dev'] = ['hd'] + context["boot_dev"] = ["hd"] - context['boot'] = boot if boot else {} + context["boot"] = boot if boot else {} - if os_type == 'xen': + if os_type == "xen": # Compute the Xen PV boot method - if __grains__['os_family'] == 'Suse': - if not boot or not boot.get('kernel', None): - context['boot']['kernel'] = \ - '/usr/lib/grub2/x86_64-xen/grub.xen' - context['boot_dev'] = [] - - if 'serial_type' in kwargs: - context['serial_type'] = kwargs['serial_type'] - if 'serial_type' in context and context['serial_type'] == 'tcp': - if 'telnet_port' in kwargs: - context['telnet_port'] = kwargs['telnet_port'] + if __grains__["os_family"] == "Suse": + if not boot or not boot.get("kernel", None): + context["boot"]["kernel"] = "/usr/lib/grub2/x86_64-xen/grub.xen" + context["boot_dev"] = [] + + if "serial_type" in kwargs: + context["serial_type"] = kwargs["serial_type"] + if "serial_type" in context and context["serial_type"] == "tcp": + if "telnet_port" in kwargs: + context["telnet_port"] = kwargs["telnet_port"] else: - context['telnet_port'] = 23023 # FIXME: use random unused port - if 'serial_type' in context: - if 'console' in kwargs: - context['console'] = kwargs['console'] + context["telnet_port"] = 23023 # FIXME: use random unused port + if "serial_type" in context: + if "console" in kwargs: + context["console"] = kwargs["console"] else: - context['console'] = True + context["console"] = True - context['disks'] = [] - disk_bus_map = {'virtio': 'vd', 'xen': 'xvd', 'fdc': 'fd', 'ide': 'hd'} + context["disks"] = [] + disk_bus_map = {"virtio": "vd", "xen": "xvd", "fdc": "fd", "ide": "hd"} + targets = [] for i, disk in enumerate(diskp): - prefix = disk_bus_map.get(disk['model'], 'sd') + prefix = disk_bus_map.get(disk["model"], "sd") disk_context = { - 'device': disk.get('device', 'disk'), - 'target_dev': '{0}{1}'.format(prefix, string.ascii_lowercase[i]), - 'disk_bus': disk['model'], - 'type': disk['format'], - 'index': six.text_type(i), + "device": disk.get("device", "disk"), + "target_dev": _get_disk_target(targets, len(diskp), prefix), + "disk_bus": disk["model"], + "format": disk.get("format", "raw"), + "index": six.text_type(i), } - if 'source_file' and disk['source_file']: - disk_context['source_file'] = disk['source_file'] - - if hypervisor in ['qemu', 'kvm', 'bhyve', 'xen']: - disk_context['address'] = False - disk_context['driver'] = True - elif hypervisor in ['esxi', 'vmware']: - disk_context['address'] = True - disk_context['driver'] = False - context['disks'].append(disk_context) - context['nics'] = nicp - - context['os_type'] = os_type - context['arch'] = arch - - fn_ = 'libvirt_domain.jinja' + targets.append(disk_context["target_dev"]) + if disk.get("source_file"): + url = urlparse(disk["source_file"]) + if not url.scheme or not url.hostname: + disk_context["source_file"] = disk["source_file"] + disk_context["type"] = "file" + elif url.scheme in ["http", "https", "ftp", "ftps", "tftp"]: + disk_context["type"] = "network" + disk_context["protocol"] = url.scheme + disk_context["volume"] = url.path + disk_context["query"] = saxutils.escape(url.query) + disk_context["hosts"] = [{"name": url.hostname, "port": url.port}] + + elif disk.get("pool"): + disk_context["volume"] = disk["filename"] + # If we had no source_file, then we want a volume + pool_xml = ElementTree.fromstring( + conn.storagePoolLookupByName(disk["pool"]).XMLDesc() + ) + pool_type = pool_xml.get("type") + if pool_type in ["rbd", "gluster", "sheepdog"]: + # libvirt can't handle rbd, gluster and sheepdog as volumes + disk_context["type"] = "network" + disk_context["protocol"] = pool_type + # Copy the hosts from the pool definition + disk_context["hosts"] = [ + {"name": host.get("name"), "port": host.get("port")} + for host in pool_xml.findall(".//host") + ] + dir_node = pool_xml.find("./source/dir") + # Gluster and RBD need pool/volume name + name_node = pool_xml.find("./source/name") + if name_node is not None: + disk_context["volume"] = "{}/{}".format( + name_node.text, disk_context["volume"] + ) + # Copy the authentication if any for RBD + auth_node = pool_xml.find("./source/auth") + if auth_node is not None: + username = auth_node.get("username") + secret_node = auth_node.find("./secret") + usage = secret_node.get("usage") + if not usage: + # Get the usage from the UUID + uuid = secret_node.get("uuid") + usage = conn.secretLookupByUUIDString(uuid).usageID() + disk_context["auth"] = { + "type": "ceph", + "username": username, + "usage": usage, + } + else: + if pool_type in ["disk", "logical"]: + # The volume format for these types doesn't match the driver format in the VM + disk_context["format"] = "raw" + disk_context["type"] = "volume" + disk_context["pool"] = disk["pool"] + + else: + # No source and no pool is a removable device, use file type + disk_context["type"] = "file" + + if hypervisor in ["qemu", "kvm", "bhyve", "xen"]: + disk_context["address"] = False + disk_context["driver"] = True + elif hypervisor in ["esxi", "vmware"]: + disk_context["address"] = True + disk_context["driver"] = False + context["disks"].append(disk_context) + context["nics"] = nicp + + context["os_type"] = os_type + context["arch"] = arch + + fn_ = "libvirt_domain.jinja" try: template = JINJA.get_template(fn_) except jinja2.exceptions.TemplateNotFound: - log.error('Could not load template %s', fn_) - return '' + log.error("Could not load template %s", fn_) + return "" return template.render(**context) -def _gen_vol_xml(vmname, - diskname, - disktype, - size, - pool): - ''' +def _gen_vol_xml( + name, + size, + format=None, + allocation=0, + type=None, + permissions=None, + backing_store=None, + nocow=False, +): + """ Generate the XML string to define a libvirt storage volume - ''' + """ size = int(size) * 1024 # MB context = { - 'name': vmname, - 'filename': '{0}.{1}'.format(diskname, disktype), - 'volname': diskname, - 'disktype': disktype, - 'size': six.text_type(size), - 'pool': pool, + "type": type, + "name": name, + "target": {"permissions": permissions, "nocow": nocow}, + "format": format, + "size": six.text_type(size), + "allocation": six.text_type(int(allocation) * 1024), + "backingStore": backing_store, } - fn_ = 'libvirt_volume.jinja' + fn_ = "libvirt_volume.jinja" try: template = JINJA.get_template(fn_) except jinja2.exceptions.TemplateNotFound: - log.error('Could not load template %s', fn_) - return '' + log.error("Could not load template %s", fn_) + return "" return template.render(**context) -def _gen_net_xml(name, - bridge, - forward, - vport, - tag=None, - ip_configs=None): - ''' +def _gen_net_xml(name, bridge, forward, vport, tag=None, ip_configs=None): + """ Generate the XML string to define a libvirt network - ''' + """ context = { - 'name': name, - 'bridge': bridge, - 'forward': forward, - 'vport': vport, - 'tag': tag, - 'ip_configs': [{ - 'address': ipaddress.ip_network(config['cidr']), - 'dhcp_ranges': config.get('dhcp_ranges', []), - } for config in ip_configs or []], + "name": name, + "bridge": bridge, + "forward": forward, + "vport": vport, + "tag": tag, + "ip_configs": [ + { + "address": ipaddress.ip_network(config["cidr"]), + "dhcp_ranges": config.get("dhcp_ranges", []), + } + for config in ip_configs or [] + ], } - fn_ = 'libvirt_network.jinja' + fn_ = "libvirt_network.jinja" try: template = JINJA.get_template(fn_) except jinja2.exceptions.TemplateNotFound: - log.error('Could not load template %s', fn_) - return '' + log.error("Could not load template %s", fn_) + return "" return template.render(**context) -def _gen_pool_xml(name, - ptype, - target=None, - permissions=None, - source_devices=None, - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None): - ''' +def _gen_pool_xml( + name, + ptype, + target=None, + permissions=None, + source_devices=None, + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, +): + """ Generate the XML string to define a libvirt storage pool - ''' - hosts = [host.split(':') for host in source_hosts or []] + """ + hosts = [host.split(":") for host in source_hosts or []] source = None - if any([source_devices, source_dir, source_adapter, hosts, source_auth, source_name, source_format, - source_initiator]): + if any( + [ + source_devices, + source_dir, + source_adapter, + hosts, + source_auth, + source_name, + source_format, + source_initiator, + ] + ): source = { - 'devices': source_devices or [], - 'dir': source_dir if source_format != 'cifs' or not source_dir else source_dir.lstrip('/'), - 'adapter': source_adapter, - 'hosts': [{'name': host[0], 'port': host[1] if len(host) > 1 else None} for host in hosts], - 'auth': source_auth, - 'name': source_name, - 'format': source_format, - 'initiator': source_initiator, + "devices": source_devices or [], + "dir": source_dir + if source_format != "cifs" or not source_dir + else source_dir.lstrip("/"), + "adapter": source_adapter, + "hosts": [ + {"name": host[0], "port": host[1] if len(host) > 1 else None} + for host in hosts + ], + "auth": source_auth, + "name": source_name, + "format": source_format, + "initiator": source_initiator, } context = { - 'name': name, - 'ptype': ptype, - 'target': {'path': target, 'permissions': permissions}, - 'source': source + "name": name, + "ptype": ptype, + "target": {"path": target, "permissions": permissions}, + "source": source, } - fn_ = 'libvirt_pool.jinja' + fn_ = "libvirt_pool.jinja" try: template = JINJA.get_template(fn_) except jinja2.exceptions.TemplateNotFound: - log.error('Could not load template %s', fn_) - return '' + log.error("Could not load template %s", fn_) + return "" return template.render(**context) def _gen_secret_xml(auth_type, usage, description): - ''' + """ Generate a libvirt secret definition XML - ''' + """ context = { - 'type': auth_type, - 'usage': usage, - 'description': description, + "type": auth_type, + "usage": usage, + "description": description, } - fn_ = 'libvirt_secret.jinja' + fn_ = "libvirt_secret.jinja" try: template = JINJA.get_template(fn_) except jinja2.exceptions.TemplateNotFound: - log.error('Could not load template %s', fn_) - return '' + log.error("Could not load template %s", fn_) + return "" return template.render(**context) def _get_images_dir(): - ''' + """ Extract the images dir from the configuration. First attempts to find legacy virt.images, then tries virt:images. - ''' - img_dir = __salt__['config.option']('virt.images') - if img_dir: - salt.utils.versions.warn_until( - 'Sodium', - '\'virt.images\' has been deprecated in favor of ' - '\'virt:images\'. \'virt.images\' will stop ' - 'being used in {version}.') - else: - img_dir = __salt__['config.get']('virt:images') - - log.debug('Image directory from config option `virt:images`' - ' is %s', img_dir) + """ + img_dir = __salt__["config.get"]("virt:images") + log.debug("Image directory from config option `virt:images`" " is %s", img_dir) return img_dir -def _qemu_image_create(disk, create_overlay=False, saltenv='base'): - ''' +def _zfs_image_create( + vm_name, + pool, + disk_name, + hostname_property_name, + sparse_volume, + disk_size, + disk_image_name, +): + """ + Clones an existing image, or creates a new one. + + When cloning an image, disk_image_name refers to the source + of the clone. If not specified, disk_size is used for creating + a new zvol, and sparse_volume determines whether to create + a thin provisioned volume. + + The cloned or new volume can have a ZFS property set containing + the vm_name. Use hostname_property_name for specifying the key + of this ZFS property. + """ + if not disk_image_name and not disk_size: + raise CommandExecutionError( + "Unable to create new disk {0}, please specify" + " the disk image name or disk size argument".format(disk_name) + ) + + if not pool: + raise CommandExecutionError( + "Unable to create new disk {0}, please specify" + " the disk pool name".format(disk_name) + ) + + destination_fs = os.path.join(pool, "{0}.{1}".format(vm_name, disk_name)) + log.debug("Image destination will be %s", destination_fs) + + existing_disk = __salt__["zfs.list"](name=pool) + if "error" in existing_disk: + raise CommandExecutionError( + "Unable to create new disk {0}. {1}".format( + destination_fs, existing_disk["error"] + ) + ) + elif destination_fs in existing_disk: + log.info( + "ZFS filesystem {0} already exists. Skipping creation".format( + destination_fs + ) + ) + blockdevice_path = os.path.join("/dev/zvol", pool, vm_name) + return blockdevice_path + + properties = {} + if hostname_property_name: + properties[hostname_property_name] = vm_name + + if disk_image_name: + __salt__["zfs.clone"]( + name_a=disk_image_name, name_b=destination_fs, properties=properties + ) + + elif disk_size: + __salt__["zfs.create"]( + name=destination_fs, + properties=properties, + volume_size=disk_size, + sparse=sparse_volume, + ) + + blockdevice_path = os.path.join( + "/dev/zvol", pool, "{0}.{1}".format(vm_name, disk_name) + ) + log.debug("Image path will be %s", blockdevice_path) + return blockdevice_path + + +def _qemu_image_create(disk, create_overlay=False, saltenv="base"): + """ Create the image file using specified disk_size or/and disk_image Return path to the created image file - ''' - disk_size = disk.get('size', None) - disk_image = disk.get('image', None) + """ + disk_size = disk.get("size", None) + disk_image = disk.get("image", None) if not disk_size and not disk_image: raise CommandExecutionError( - 'Unable to create new disk {0}, please specify' - ' disk size and/or disk image argument' - .format(disk['filename']) + "Unable to create new disk {0}, please specify" + " disk size and/or disk image argument".format(disk["filename"]) ) - img_dest = disk['source_file'] - log.debug('Image destination will be %s', img_dest) + img_dest = disk["source_file"] + log.debug("Image destination will be %s", img_dest) img_dir = os.path.dirname(img_dest) - log.debug('Image destination directory is %s', img_dir) + log.debug("Image destination directory is %s", img_dir) if not os.path.exists(img_dir): os.makedirs(img_dir) if disk_image: - log.debug('Create disk from specified image %s', disk_image) - sfn = __salt__['cp.cache_file'](disk_image, saltenv) + log.debug("Create disk from specified image %s", disk_image) + sfn = __salt__["cp.cache_file"](disk_image, saltenv) qcow2 = False - if salt.utils.path.which('qemu-img'): - res = __salt__['cmd.run']('qemu-img info "{}"'.format(sfn)) + if salt.utils.path.which("qemu-img"): + res = __salt__["cmd.run"]('qemu-img info "{}"'.format(sfn)) imageinfo = salt.utils.yaml.safe_load(res) - qcow2 = imageinfo['file format'] == 'qcow2' + qcow2 = imageinfo["file format"] == "qcow2" try: if create_overlay and qcow2: - log.info('Cloning qcow2 image %s using copy on write', sfn) - __salt__['cmd.run']( - 'qemu-img create -f qcow2 -o backing_file="{0}" "{1}"' - .format(sfn, img_dest).split()) + log.info("Cloning qcow2 image %s using copy on write", sfn) + __salt__["cmd.run"]( + 'qemu-img create -f qcow2 -o backing_file="{0}" "{1}"'.format( + sfn, img_dest + ).split() + ) else: - log.debug('Copying %s to %s', sfn, img_dest) + log.debug("Copying %s to %s", sfn, img_dest) salt.utils.files.copyfile(sfn, img_dest) mask = salt.utils.files.get_umask() if disk_size and qcow2: - log.debug('Resize qcow2 image to %sM', disk_size) - __salt__['cmd.run']( - 'qemu-img resize "{0}" {1}M' - .format(img_dest, disk_size) + log.debug("Resize qcow2 image to %sM", disk_size) + __salt__["cmd.run"]( + 'qemu-img resize "{0}" {1}M'.format(img_dest, disk_size) ) - log.debug('Apply umask and remove exec bit') + log.debug("Apply umask and remove exec bit") mode = (0o0777 ^ mask) & 0o0666 os.chmod(img_dest, mode) except (IOError, OSError) as err: raise CommandExecutionError( - 'Problem while copying image. {0} - {1}' - .format(disk_image, err) + "Problem while copying image. {0} - {1}".format(disk_image, err) ) else: @@ -839,33 +1097,105 @@ def _qemu_image_create(disk, create_overlay=False, saltenv='base'): mask = salt.utils.files.get_umask() if disk_size: - log.debug('Create empty image with size %sM', disk_size) - __salt__['cmd.run']( - 'qemu-img create -f {0} "{1}" {2}M' - .format(disk.get('format', 'qcow2'), img_dest, disk_size) + log.debug("Create empty image with size %sM", disk_size) + __salt__["cmd.run"]( + 'qemu-img create -f {0} "{1}" {2}M'.format( + disk.get("format", "qcow2"), img_dest, disk_size + ) ) else: raise CommandExecutionError( - 'Unable to create new disk {0},' - ' please specify <size> argument' - .format(img_dest) + "Unable to create new disk {0}," + " please specify <size> argument".format(img_dest) ) - log.debug('Apply umask and remove exec bit') + log.debug("Apply umask and remove exec bit") mode = (0o0777 ^ mask) & 0o0666 os.chmod(img_dest, mode) except (IOError, OSError) as err: raise CommandExecutionError( - 'Problem while creating volume {0} - {1}' - .format(img_dest, err) + "Problem while creating volume {0} - {1}".format(img_dest, err) ) return img_dest -def _disk_profile(profile, hypervisor, disks=None, vm_name=None, image=None, pool=None, **kwargs): - ''' +def _seed_image(seed_cmd, img_path, name, config, install, pub_key, priv_key): + """ + Helper function to seed an existing image. Note that this doesn't + handle volumes. + """ + log.debug("Seeding image") + __salt__[seed_cmd]( + img_path, + id_=name, + config=config, + install=install, + pub_key=pub_key, + priv_key=priv_key, + ) + + +def _disk_volume_create(conn, disk, seeder=None, saltenv="base"): + """ + Create a disk volume for use in a VM + """ + if disk.get("overlay_image"): + raise SaltInvocationError( + "Disk overlay_image property is not supported when creating volumes," + "use backing_store_path and backing_store_format instead." + ) + + pool = conn.storagePoolLookupByName(disk["pool"]) + + # Use existing volume if possible + if disk["filename"] in pool.listVolumes(): + return + + pool_type = ElementTree.fromstring(pool.XMLDesc()).get("type") + + backing_path = disk.get("backing_store_path") + backing_format = disk.get("backing_store_format") + backing_store = None + if ( + backing_path + and backing_format + and (disk.get("format") == "qcow2" or pool_type == "logical") + ): + backing_store = {"path": backing_path, "format": backing_format} + + if backing_store and disk.get("image"): + raise SaltInvocationError( + "Using a template image with a backing store is not possible, " + "choose either of them." + ) + + vol_xml = _gen_vol_xml( + disk["filename"], + disk.get("size", 0), + format=disk.get("format"), + backing_store=backing_store, + ) + _define_vol_xml_str(conn, vol_xml, disk.get("pool")) + + if disk.get("image"): + log.debug("Caching disk template image: %s", disk.get("image")) + cached_path = __salt__["cp.cache_file"](disk.get("image"), saltenv) + + if seeder: + seeder(cached_path) + _volume_upload( + conn, + disk["pool"], + disk["filename"], + cached_path, + sparse=disk.get("format") == "qcow2", + ) + + +def _disk_profile(conn, profile, hypervisor, disks, vm_name): + """ Gather the disk profile from the config or apply the default based on the active hypervisor @@ -902,22 +1232,16 @@ def _disk_profile(profile, hypervisor, disks=None, vm_name=None, image=None, poo The ``format`` and ``model`` parameters are optional, and will default to whatever is best suitable for the active hypervisor. - ''' - default = [{'system': - {'size': 8192}}] - if hypervisor == 'vmware': - overlay = {'format': 'vmdk', - 'model': 'scsi', - 'device': 'disk', - 'pool': '[{0}] '.format(pool if pool else '0')} - elif hypervisor in ['qemu', 'kvm']: - overlay = {'format': 'qcow2', - 'device': 'disk', - 'model': 'virtio'} - elif hypervisor == 'xen': - overlay = {'format': 'qcow2', - 'device': 'disk', - 'model': 'xen'} + """ + default = [{"system": {"size": 8192}}] + if hypervisor == "vmware": + overlay = {"format": "vmdk", "model": "scsi", "device": "disk"} + elif hypervisor in ["qemu", "kvm"]: + overlay = {"device": "disk", "model": "virtio"} + elif hypervisor == "xen": + overlay = {"device": "disk", "model": "xen"} + elif hypervisor in ["bhyve"]: + overlay = {"format": "raw", "model": "virtio", "sparse_volume": False} else: overlay = {} @@ -925,89 +1249,153 @@ def _disk_profile(profile, hypervisor, disks=None, vm_name=None, image=None, poo disklist = [] if profile: disklist = copy.deepcopy( - __salt__['config.get']('virt:disk', {}).get(profile, default)) + __salt__["config.get"]("virt:disk", {}).get(profile, default) + ) # Transform the list to remove one level of dictionnary and add the name as a property disklist = [dict(d, name=name) for disk in disklist for name, d in disk.items()] - # Add the image to the first disk if there is one - if image: - # If image is specified in module arguments, then it will be used - # for the first disk instead of the image from the disk profile - log.debug('%s image from module arguments will be used for disk "%s"' - ' instead of %s', image, disklist[0]['name'], disklist[0].get('image', "")) - disklist[0]['image'] = image - # Merge with the user-provided disks definitions if disks: for udisk in disks: - if 'name' in udisk: - found = [disk for disk in disklist if udisk['name'] == disk['name']] + if "name" in udisk: + found = [disk for disk in disklist if udisk["name"] == disk["name"]] if found: found[0].update(udisk) else: disklist.append(udisk) + # Get pool capabilities once to get default format later + pool_caps = _pool_capabilities(conn) + for disk in disklist: + # Set default model for cdrom devices before the overlay sets the wrong one + if disk.get("device", "disk") == "cdrom" and "model" not in disk: + disk["model"] = "ide" + # Add the missing properties that have defaults for key, val in six.iteritems(overlay): if key not in disk: disk[key] = val # We may have an already computed source_file (i.e. image not created by our module) - if 'source_file' in disk and disk['source_file']: - disk['filename'] = os.path.basename(disk['source_file']) - elif 'source_file' not in disk: - _fill_disk_filename(vm_name, disk, hypervisor, **kwargs) + if disk.get("source_file") and os.path.exists(disk["source_file"]): + disk["filename"] = os.path.basename(disk["source_file"]) + if not disk.get("format"): + disk["format"] = ( + "qcow2" if disk.get("device", "disk") != "cdrom" else "raw" + ) + elif disk.get("device", "disk") == "disk": + _fill_disk_filename(conn, vm_name, disk, hypervisor, pool_caps) return disklist -def _fill_disk_filename(vm_name, disk, hypervisor, **kwargs): - ''' +def _fill_disk_filename(conn, vm_name, disk, hypervisor, pool_caps): + """ Compute the disk file name and update it in the disk value. - ''' - base_dir = disk.get('pool', None) - if hypervisor in ['qemu', 'kvm', 'xen']: + """ + # Compute the filename without extension since it may not make sense for some pool types + disk["filename"] = "{0}_{1}".format(vm_name, disk["name"]) + + # Compute the source file path + base_dir = disk.get("pool", None) + if hypervisor in ["qemu", "kvm", "xen"]: # Compute the base directory from the pool property. We may have either a path # or a libvirt pool name there. - # If the pool is a known libvirt one with a target path, use it as target path if not base_dir: base_dir = _get_images_dir() + + # If the pool is a known libvirt one, skip the filename since a libvirt volume will be created later + if base_dir not in conn.listStoragePools(): + # For path-based disks, keep the qcow2 default format + if not disk.get("format"): + disk["format"] = "qcow2" + disk["filename"] = "{0}.{1}".format(disk["filename"], disk["format"]) + disk["source_file"] = os.path.join(base_dir, disk["filename"]) else: - if not base_dir.startswith('/'): - # The pool seems not to be a path, lookup for pool infos - infos = pool_info(base_dir, **kwargs) - pool = infos[base_dir] if base_dir in infos else None - if not pool or not pool['target_path'] or pool['target_path'].startswith('/dev'): - raise CommandExecutionError( - 'Unable to create new disk {0}, specified pool {1} does not exist ' - 'or is unsupported'.format(disk['name'], base_dir)) - base_dir = pool['target_path'] + if "pool" not in disk: + disk["pool"] = base_dir + pool_obj = conn.storagePoolLookupByName(base_dir) + pool_xml = ElementTree.fromstring(pool_obj.XMLDesc()) + pool_type = pool_xml.get("type") + + # Disk pools volume names are partition names, they need to be named based on the device name + if pool_type == "disk": + device = pool_xml.find("./source/device").get("path") + all_volumes = pool_obj.listVolumes() + if disk.get("source_file") not in all_volumes: + indexes = [ + int(re.sub("[a-z]+", "", vol_name)) for vol_name in all_volumes + ] or [0] + index = min( + [ + idx + for idx in range(1, max(indexes) + 2) + if idx not in indexes + ] + ) + disk["filename"] = "{}{}".format(os.path.basename(device), index) + + # Is the user wanting to reuse an existing volume? + if disk.get("source_file"): + if not disk.get("source_file") in pool_obj.listVolumes(): + raise SaltInvocationError( + "{} volume doesn't exist in pool {}".format( + disk.get("source_file"), base_dir + ) + ) + disk["filename"] = disk["source_file"] + del disk["source_file"] + + # Get the default format from the pool capabilities + if not disk.get("format"): + volume_options = ( + [ + type_caps.get("options", {}).get("volume", {}) + for type_caps in pool_caps.get("pool_types") + if type_caps["name"] == pool_type + ] + or [{}] + )[0] + # Still prefer qcow2 if possible + if "qcow2" in volume_options.get("targetFormatType", []): + disk["format"] = "qcow2" + else: + disk["format"] = volume_options.get("default_format", None) - # Compute the filename and source file properties if possible - if vm_name: - disk['filename'] = '{0}_{1}.{2}'.format(vm_name, disk['name'], disk['format']) - disk['source_file'] = os.path.join(base_dir, disk['filename']) + elif hypervisor == "bhyve" and vm_name: + disk["filename"] = "{0}.{1}".format(vm_name, disk["name"]) + disk["source_file"] = os.path.join( + "/dev/zvol", base_dir or "", disk["filename"] + ) + + elif hypervisor in ["esxi", "vmware"]: + if not base_dir: + base_dir = __salt__["config.get"]("virt:storagepool", "[0] ") + disk["filename"] = "{0}.{1}".format(disk["filename"], disk["format"]) + disk["source_file"] = "{0}{1}".format(base_dir, disk["filename"]) -def _complete_nics(interfaces, hypervisor, dmac=None): - ''' +def _complete_nics(interfaces, hypervisor): + """ Complete missing data for network interfaces. - ''' + """ - vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'} - kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'} - xen_overlay = {'type': 'bridge', 'source': 'br0', 'model': None} + vmware_overlay = {"type": "bridge", "source": "DEFAULT", "model": "e1000"} + kvm_overlay = {"type": "bridge", "source": "br0", "model": "virtio"} + xen_overlay = {"type": "bridge", "source": "br0", "model": None} + bhyve_overlay = {"type": "bridge", "source": "bridge0", "model": "virtio"} overlays = { - 'xen': xen_overlay, - 'kvm': kvm_overlay, - 'qemu': kvm_overlay, - 'vmware': vmware_overlay, - } + "xen": xen_overlay, + "kvm": kvm_overlay, + "qemu": kvm_overlay, + "vmware": vmware_overlay, + "bhyve": bhyve_overlay, + } def _normalize_net_types(attributes): - ''' + """ Guess which style of definition: bridge: br0 @@ -1020,84 +1408,49 @@ def _complete_nics(interfaces, hypervisor, dmac=None): type: network source: net0 - ''' - for type_ in ['bridge', 'network']: + """ + for type_ in ["bridge", "network"]: if type_ in attributes: - attributes['type'] = type_ + attributes["type"] = type_ # we want to discard the original key - attributes['source'] = attributes.pop(type_) + attributes["source"] = attributes.pop(type_) - attributes['type'] = attributes.get('type', None) - attributes['source'] = attributes.get('source', None) + attributes["type"] = attributes.get("type", None) + attributes["source"] = attributes.get("source", None) def _apply_default_overlay(attributes): - ''' + """ Apply the default overlay to attributes - ''' + """ for key, value in six.iteritems(overlays[hypervisor]): if key not in attributes or not attributes[key]: attributes[key] = value - def _assign_mac(attributes, hypervisor): - ''' - Compute mac address for NIC depending on hypervisor - ''' - if dmac is not None: - log.debug('Default MAC address is %s', dmac) - if salt.utils.validate.net.mac(dmac): - attributes['mac'] = dmac - else: - msg = 'Malformed MAC address: {0}'.format(dmac) - raise CommandExecutionError(msg) - else: - if hypervisor in ['qemu', 'kvm']: - attributes['mac'] = salt.utils.network.gen_mac( - prefix='52:54:00') - else: - attributes['mac'] = salt.utils.network.gen_mac() - for interface in interfaces: _normalize_net_types(interface) - if interface.get('mac', None) is None: - _assign_mac(interface, hypervisor) if hypervisor in overlays: _apply_default_overlay(interface) return interfaces -def _nic_profile(profile_name, hypervisor, dmac=None): - ''' +def _nic_profile(profile_name, hypervisor): + """ Compute NIC data based on profile - ''' - - default = [{'eth0': {}}] - - # support old location - config_data = __salt__['config.option']('virt.nic', {}).get( - profile_name, None + """ + config_data = __salt__["config.get"]("virt:nic", {}).get( + profile_name, [{"eth0": {}}] ) - if config_data is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'virt.nic\' has been deprecated in favor of \'virt:nic\'. ' - '\'virt.nic\' will stop being used in {version}.' - ) - else: - config_data = __salt__['config.get']('virt:nic', {}).get( - profile_name, default - ) - interfaces = [] # pylint: disable=invalid-name def append_dict_profile_to_interface_list(profile_dict): - ''' + """ Append dictionary profile data to interfaces list - ''' + """ for interface_name, attributes in six.iteritems(profile_dict): - attributes['name'] = interface_name + attributes["name"] = interface_name interfaces.append(attributes) # old style dicts (top-level dicts) @@ -1134,25 +1487,24 @@ def _nic_profile(profile_name, hypervisor, dmac=None): else: interfaces.append(interface) - # dmac can only be used from init() - return _complete_nics(interfaces, hypervisor, dmac=dmac) + return _complete_nics(interfaces, hypervisor) -def _get_merged_nics(hypervisor, profile, interfaces=None, dmac=None): - ''' +def _get_merged_nics(hypervisor, profile, interfaces=None): + """ Get network devices from the profile and merge uer defined ones with them. - ''' - nicp = _nic_profile(profile, hypervisor, dmac=dmac) if profile else [] - log.debug('NIC profile is %s', nicp) + """ + nicp = _nic_profile(profile, hypervisor) if profile else [] + log.debug("NIC profile is %s", nicp) if interfaces: users_nics = _complete_nics(interfaces, hypervisor) for unic in users_nics: - found = [nic for nic in nicp if nic['name'] == unic['name']] + found = [nic for nic in nicp if nic["name"] == unic["name"]] if found: found[0].update(unic) else: nicp.append(unic) - log.debug('Merged NICs: %s', nicp) + log.debug("Merged NICs: %s", nicp) return nicp @@ -1168,62 +1520,60 @@ def _handle_remote_boot_params(orig_boot): """ saltinst_dir = None new_boot = orig_boot.copy() + keys = orig_boot.keys() + cases = [ + {"loader", "nvram"}, + {"kernel", "initrd"}, + {"kernel", "initrd", "cmdline"}, + {"loader", "nvram", "kernel", "initrd"}, + {"loader", "nvram", "kernel", "initrd", "cmdline"}, + ] try: - for key in ['kernel', 'initrd']: - if check_remote(orig_boot.get(key)): - if saltinst_dir is None: - os.makedirs(CACHE_DIR) - saltinst_dir = CACHE_DIR - - new_boot[key] = download_remote(orig_boot.get(key), - saltinst_dir) - - return new_boot + if keys in cases: + for key in keys: + if orig_boot.get(key) is not None and check_remote(orig_boot.get(key)): + if saltinst_dir is None: + os.makedirs(CACHE_DIR) + saltinst_dir = CACHE_DIR + new_boot[key] = download_remote(orig_boot.get(key), saltinst_dir) + return new_boot + else: + raise SaltInvocationError( + "Invalid boot parameters, (kernel, initrd) or/and (loader, nvram) must be both present" + ) except Exception as err: # pylint: disable=broad-except raise err -def init(name, - cpu, - mem, - image=None, - nic='default', - interfaces=None, - hypervisor=None, - start=True, # pylint: disable=redefined-outer-name - disk='default', - disks=None, - saltenv='base', - seed=True, - install=True, - pub_key=None, - priv_key=None, - seed_cmd='seed.apply', - enable_vnc=False, - enable_qcow=False, - graphics=None, - os_type=None, - arch=None, - boot=None, - **kwargs): - ''' +def init( + name, + cpu, + mem, + nic="default", + interfaces=None, + hypervisor=None, + start=True, # pylint: disable=redefined-outer-name + disk="default", + disks=None, + saltenv="base", + seed=True, + install=True, + pub_key=None, + priv_key=None, + seed_cmd="seed.apply", + graphics=None, + os_type=None, + arch=None, + boot=None, + **kwargs +): + """ Initialize a new vm :param name: name of the virtual machine to create :param cpu: Number of virtual CPUs to assign to the virtual machine :param mem: Amount of memory to allocate to the virtual machine in MiB. - :param image: Path to a disk image to use as the first disk (Default: ``None``). - Deprecated in favor of the ``disks`` parameter. To set (or change) the image of a - disk, add the following to the disks definitions: - - .. code-block:: python - - { - 'name': 'name_of_disk_to_change', - 'image': '/path/to/the/image' - } - :param nic: NIC profile to use (Default: ``'default'``). The profile interfaces can be customized / extended with the interfaces parameter. If set to ``None``, no profile will be used. @@ -1250,17 +1600,6 @@ def init(name, :param pub_key: public key to seed with (Default: ``None``) :param priv_key: public key to seed with (Default: ``None``) :param seed_cmd: Salt command to execute to seed the image. (Default: ``'seed.apply'``) - :param enable_vnc: - ``True`` to setup a vnc display for the VM (Default: ``False``) - - Deprecated in favor of the ``graphics`` parameter. Could be replaced with - the following: - - .. code-block:: python - - graphics={'type': 'vnc'} - - .. deprecated:: 2019.2.0 :param graphics: Dictionary providing details on the graphics device to create. (Default: ``None``) See :ref:`init-graphics-def` for more details on the possible values. @@ -1276,51 +1615,6 @@ def init(name, but ``x86_64`` is prefed over ``i686``. .. versionadded:: 2019.2.0 - :param enable_qcow: - ``True`` to create a QCOW2 overlay image, rather than copying the image - (Default: ``False``). - - Deprecated in favor of ``disks`` parameter. Add the following to the disks - definitions to create an overlay image of a template disk image with an - image set: - - .. code-block:: python - - { - 'name': 'name_of_disk_to_change', - 'overlay_image': True - } - - .. deprecated:: 2019.2.0 - :param pool: - Path of the folder where the image files are located for vmware/esx hypervisors. - - Deprecated in favor of ``disks`` parameter. Add the following to the disks - definitions to set the vmware datastore of a disk image: - - .. code-block:: python - - { - 'name': 'name_of_disk_to_change', - 'pool': 'mydatastore' - } - - .. deprecated:: Flurorine - :param dmac: - Default MAC address to use for the network interfaces. By default MAC addresses are - automatically generated. - - Deprecated in favor of ``interfaces`` parameter. Add the following to the interfaces - definitions to force the mac address of a NIC: - - .. code-block:: python - - { - 'name': 'name_of_nic_to_change', - 'mac': 'MY:MA:CC:ADD:RE:SS' - } - - .. deprecated:: 2019.2.0 :param config: minion configuration to use when seeding. See :mod:`seed module for more details <salt.modules.seed>` :param boot_dev: String of space-separated devices to boot from (Default: ``'hd'``) @@ -1337,21 +1631,48 @@ def init(name, .. versionadded:: 2019.2.0 :param boot: - Specifies kernel for the virtual machine, as well as boot parameters - for the virtual machine. This is an optionl parameter, and all of the - keys are optional within the dictionary. If a remote path is provided - to kernel or initrd, salt will handle the downloading of the specified - remote fild, and will modify the XML accordingly. + Specifies kernel, initial ramdisk and kernel command line parameters for the virtual machine. + This is an optional parameter, all of the keys are optional within the dictionary. The structure of + the dictionary is documented in :ref:`init-boot-def`. If a remote path is provided to kernel or initrd, + salt will handle the downloading of the specified remote file and modify the XML accordingly. + To boot VM with UEFI, specify loader and nvram path. + + .. versionadded:: 3000 .. code-block:: python { 'kernel': '/root/f8-i386-vmlinuz', 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/', + 'loader': '/usr/share/OVMF/OVMF_CODE.fd', + 'nvram': '/usr/share/OVMF/OVMF_VARS.ms.fd' } - .. versionadded:: 3000 + .. _init-boot-def: + + .. rubric:: Boot parameters definition + + The boot parameters dictionary can contains the following properties: + + kernel + The URL or path to the kernel to run the virtual machine with. + + initrd + The URL or path to the initrd file to run the virtual machine with. + + cmdline + The parameters to pass to the kernel provided in the `kernel` property. + + loader + The path to the UEFI binary loader to use. + + .. versionadded:: sodium + + nvram + The path to the UEFI data template. The file will be copied when creating the virtual machine. + + .. versionadded:: sodium .. _init-nic-def: @@ -1393,7 +1714,12 @@ def init(name, pool Path to the folder or name of the pool where disks should be created. - (Default: depends on hypervisor) + (Default: depends on hypervisor and the virt:storagepool configuration) + + .. versionchanged:: sodium + + If the value contains no '/', it is considered a pool name where to create a volume. + Using volumes will be mandatory for some pools types like rdb, iscsi, etc. model One of the disk busses allowed by libvirt (Default: depends on hypervisor) @@ -1404,19 +1730,69 @@ def init(name, Path to the image to use for the disk. If no image is provided, an empty disk will be created (Default: ``None``) + Note that some pool types do not support uploading an image. This list can evolve with libvirt + versions. + overlay_image ``True`` to create a QCOW2 disk image with ``image`` as backing file. If ``False`` the file pointed to by the ``image`` property will simply be copied. (Default: ``False``) + .. versionchanged:: sodium + + This property is only valid on path-based disks, not on volumes. To create a volume with a + backing store, set the ``backing_store_path`` and ``backing_store_format`` properties. + + backing_store_path + Path to the backing store image to use. This can also be the name of a volume to use as + backing store within the same pool. + + .. versionadded:: sodium + + backing_store_format + Image format of the disk or volume to use as backing store. This property is mandatory when + using ``backing_store_path`` to avoid `problems <https://libvirt.org/kbase/backing_chains.html#troubleshooting>`_ + + .. versionadded:: sodium + source_file Absolute path to the disk image to use. Not to be confused with ``image`` parameter. This parameter is useful to use disk images that are created outside of this module. Can also be ``None`` for devices that have no associated image like cdroms. + .. versionchanged:: sodium + + For volume disks, this can be the name of a volume already existing in the storage pool. + device Type of device of the disk. Can be one of 'disk', 'cdrom', 'floppy' or 'lun'. (Default: ``'disk'``) + hostname_property + When using ZFS volumes, setting this value to a ZFS property ID will make Salt store the name of the + virtual machine inside this property. (Default: ``None``) + + sparse_volume + Boolean to specify whether to use a thin provisioned ZFS volume. + + Example profile for a bhyve VM with two ZFS disks. The first is + cloned from the specified image. The second disk is a thin + provisioned volume. + + .. code-block:: yaml + + virt: + disk: + two_zvols: + - system: + image: zroot/bhyve/CentOS-7-x86_64-v1@v1.0.5 + hostname_property: virt:hostname + pool: zroot/bhyve/guests + - data: + pool: tank/disks + size: 20G + hostname_property: virt:hostname + sparse_volume: True + .. _init-graphics-def: .. rubric:: Graphics Definition @@ -1462,162 +1838,142 @@ def init(name, .. _disk element: https://libvirt.org/formatdomain.html#elementsDisks .. _graphics element: https://libvirt.org/formatdomain.html#elementsGraphics - ''' - caps = capabilities(**kwargs) - os_types = sorted({guest['os_type'] for guest in caps['guests']}) - arches = sorted({guest['arch']['name'] for guest in caps['guests']}) - if not hypervisor: - hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor) - if hypervisor is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'libvirt:hypervisor\' configuration property has been deprecated. ' - 'Rather use the \'virt:connection:uri\' to properly define the libvirt ' - 'URI or alias of the host to connect to. \'libvirt:hypervisor\' will ' - 'stop being used in {version}.' - ) - else: + """ + try: + conn = __get_conn(**kwargs) + caps = _capabilities(conn) + os_types = sorted({guest["os_type"] for guest in caps["guests"]}) + arches = sorted({guest["arch"]["name"] for guest in caps["guests"]}) + + virt_hypervisor = hypervisor + if not virt_hypervisor: # Use the machine types as possible values # Prefer 'kvm' over the others if available - hypervisors = sorted({x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y}) - hypervisor = 'kvm' if 'kvm' in hypervisors else hypervisors[0] - - # esxi used to be a possible value for the hypervisor: map it to vmware since it's the same - hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor - - log.debug('Using hypervisor %s', hypervisor) - - # the NICs are computed as follows: - # 1 - get the default NICs from the profile - # 2 - Complete the users NICS - # 3 - Update the default NICS list to the users one, matching key is the name - dmac = kwargs.get('dmac', None) - if dmac: - salt.utils.versions.warn_until( - 'Sodium', - '\'dmac\' parameter has been deprecated. Rather use the \'interfaces\' parameter ' - 'to properly define the desired MAC address. \'dmac\' will be removed in {version}.' - ) - nicp = _get_merged_nics(hypervisor, nic, interfaces, dmac=dmac) - - # the disks are computed as follows: - # 1 - get the disks defined in the profile - # 2 - set the image on the first disk (will be removed later) - # 3 - update the disks from the profile with the ones from the user. The matching key is the name. - pool = kwargs.get('pool', None) - if pool: - salt.utils.versions.warn_until( - 'Sodium', - '\'pool\' parameter has been deprecated. Rather use the \'disks\' parameter ' - 'to properly define the vmware datastore of disks. \'pool\' will be removed in {version}.' - ) + hypervisors = sorted( + { + x + for y in [ + guest["arch"]["domains"].keys() for guest in caps["guests"] + ] + for x in y + } + ) + virt_hypervisor = "kvm" if "kvm" in hypervisors else hypervisors[0] - if image: - salt.utils.versions.warn_until( - 'Sodium', - '\'image\' parameter has been deprecated. Rather use the \'disks\' parameter ' - 'to override or define the image. \'image\' will be removed in {version}.' - ) + # esxi used to be a possible value for the hypervisor: map it to vmware since it's the same + virt_hypervisor = "vmware" if virt_hypervisor == "esxi" else virt_hypervisor - diskp = _disk_profile(disk, hypervisor, disks, name, image=image, pool=pool, **kwargs) + log.debug("Using hypervisor %s", virt_hypervisor) - # Create multiple disks, empty or from specified images. - for _disk in diskp: - log.debug("Creating disk for VM [ %s ]: %s", name, _disk) + nicp = _get_merged_nics(virt_hypervisor, nic, interfaces) - if hypervisor == 'vmware': - if 'image' in _disk: - # TODO: we should be copying the image file onto the ESX host - raise SaltInvocationError( - 'virt.init does not support image ' - 'template in conjunction with esxi hypervisor' - ) - else: - # assume libvirt manages disks for us - log.debug('Generating libvirt XML for %s', _disk) - vol_xml = _gen_vol_xml( - name, - _disk['name'], - _disk['format'], - _disk['size'], - _disk['pool'] - ) - define_vol_xml_str(vol_xml) + # the disks are computed as follows: + # 1 - get the disks defined in the profile + # 3 - update the disks from the profile with the ones from the user. The matching key is the name. + diskp = _disk_profile(conn, disk, virt_hypervisor, disks, name) - elif hypervisor in ['qemu', 'kvm', 'xen']: + # Create multiple disks, empty or from specified images. + for _disk in diskp: + # No need to create an image for cdrom devices + if _disk.get("device", "disk") == "cdrom": + continue - create_overlay = enable_qcow - if create_overlay: - salt.utils.versions.warn_until( - 'Sodium', - '\'enable_qcow\' parameter has been deprecated. Rather use the \'disks\' ' - 'parameter to override or define the image. \'enable_qcow\' will be removed ' - 'in {version}.' - ) - else: - create_overlay = _disk.get('overlay_image', False) + log.debug("Creating disk for VM [ %s ]: %s", name, _disk) - if _disk['source_file']: - if os.path.exists(_disk['source_file']): - img_dest = _disk['source_file'] + if virt_hypervisor == "vmware": + if "image" in _disk: + # TODO: we should be copying the image file onto the ESX host + raise SaltInvocationError( + "virt.init does not support image " + "template in conjunction with esxi hypervisor" + ) else: - img_dest = _qemu_image_create(_disk, create_overlay, saltenv) - else: - img_dest = None - - # Seed only if there is an image specified - if seed and img_dest and _disk.get('image', None): - log.debug('Seed command is %s', seed_cmd) - __salt__[seed_cmd]( - img_dest, - id_=name, - config=kwargs.get('config'), - install=install, - pub_key=pub_key, - priv_key=priv_key, + # assume libvirt manages disks for us + log.debug("Generating libvirt XML for %s", _disk) + volume_name = "{0}/{1}".format(name, _disk["name"]) + filename = "{0}.{1}".format(volume_name, _disk["format"]) + vol_xml = _gen_vol_xml( + filename, _disk["size"], format=_disk["format"] + ) + _define_vol_xml_str(conn, vol_xml, pool=_disk.get("pool")) + + elif virt_hypervisor in ["qemu", "kvm", "xen"]: + + def seeder(path): + _seed_image( + seed_cmd, + path, + name, + kwargs.get("config"), + install, + pub_key, + priv_key, + ) + + create_overlay = _disk.get("overlay_image", False) + format = _disk.get("format") + if _disk.get("source_file"): + if os.path.exists(_disk["source_file"]): + img_dest = _disk["source_file"] + else: + img_dest = _qemu_image_create(_disk, create_overlay, saltenv) + else: + _disk_volume_create(conn, _disk, seeder if seed else None, saltenv) + img_dest = None + + # Seed only if there is an image specified + if seed and img_dest and _disk.get("image", None): + seeder(img_dest) + + elif hypervisor in ["bhyve"]: + img_dest = _zfs_image_create( + vm_name=name, + pool=_disk.get("pool"), + disk_name=_disk.get("name"), + disk_size=_disk.get("size"), + disk_image_name=_disk.get("image"), + hostname_property_name=_disk.get("hostname_property"), + sparse_volume=_disk.get("sparse_volume"), ) - else: - # Unknown hypervisor - raise SaltInvocationError( - 'Unsupported hypervisor when handling disk image: {0}' - .format(hypervisor) - ) - - log.debug('Generating VM XML') + else: + # Unknown hypervisor + raise SaltInvocationError( + "Unsupported hypervisor when handling disk image: {0}".format( + virt_hypervisor + ) + ) - if enable_vnc: - salt.utils.versions.warn_until( - 'Sodium', - '\'enable_vnc\' parameter has been deprecated in favor of ' - '\'graphics\'. Use graphics={\'type\': \'vnc\'} for the same behavior. ' - '\'enable_vnc\' will be removed in {version}. ') - graphics = {'type': 'vnc'} + log.debug("Generating VM XML") + if os_type is None: + os_type = "hvm" if "hvm" in os_types else os_types[0] + if arch is None: + arch = "x86_64" if "x86_64" in arches else arches[0] - if os_type is None: - os_type = 'hvm' if 'hvm' in os_types else os_types[0] - if arch is None: - arch = 'x86_64' if 'x86_64' in arches else arches[0] + if boot is not None: + boot = _handle_remote_boot_params(boot) - if boot is not None: - boot = _handle_remote_boot_params(boot) - - vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, os_type, arch, - graphics, boot, **kwargs) - conn = __get_conn(**kwargs) - try: + vm_xml = _gen_xml( + conn, + name, + cpu, + mem, + diskp, + nicp, + virt_hypervisor, + os_type, + arch, + graphics, + boot, + **kwargs + ) conn.defineXML(vm_xml) - except libvirtError as err: - # check if failure is due to this domain already existing - if "domain '{}' already exists".format(name) in six.text_type(err): - # continue on to seeding - log.warning(err) - else: - conn.close() - raise err # a real error we should report upwards + except libvirt.libvirtError as err: + conn.close() + raise CommandExecutionError(err.get_error_message()) if start: - log.debug('Starting VM %s', name) + log.debug("Starting VM %s", name) _get_domain(conn, name).create() conn.close() @@ -1625,66 +1981,107 @@ def init(name, def _disks_equal(disk1, disk2): - ''' + """ Test if two disk elements should be considered like the same device - ''' - target1 = disk1.find('target') - target2 = disk2.find('target') - source1 = ElementTree.tostring(disk1.find('source')) if disk1.find('source') is not None else None - source2 = ElementTree.tostring(disk2.find('source')) if disk2.find('source') is not None else None + """ + target1 = disk1.find("target") + target2 = disk2.find("target") + source1 = ( + disk1.find("source") + if disk1.find("source") is not None + else ElementTree.Element("source") + ) + source2 = ( + disk2.find("source") + if disk2.find("source") is not None + else ElementTree.Element("source") + ) - return source1 == source2 and \ - target1 is not None and target2 is not None and \ - target1.get('bus') == target2.get('bus') and \ - disk1.get('device', 'disk') == disk2.get('device', 'disk') and \ - target1.get('dev') == target2.get('dev') + source1_dict = xmlutil.to_dict(source1, True) + source2_dict = xmlutil.to_dict(source2, True) + + # Remove the index added by libvirt in the source for backing chain + if source1_dict: + source1_dict.pop("index", None) + if source2_dict: + source2_dict.pop("index", None) + + return ( + source1_dict == source2_dict + and target1 is not None + and target2 is not None + and target1.get("bus") == target2.get("bus") + and disk1.get("device", "disk") == disk2.get("device", "disk") + and target1.get("dev") == target2.get("dev") + ) def _nics_equal(nic1, nic2): - ''' + """ Test if two interface elements should be considered like the same device - ''' + """ def _filter_nic(nic): - ''' + """ Filter out elements to ignore when comparing nics - ''' + """ return { - 'type': nic.attrib['type'], - 'source': nic.find('source').attrib[nic.attrib['type']] if nic.find('source') is not None else None, - 'mac': nic.find('mac').attrib['address'].lower() if nic.find('mac') is not None else None, - 'model': nic.find('model').attrib['type'] if nic.find('model') is not None else None, + "type": nic.attrib["type"], + "source": nic.find("source").attrib[nic.attrib["type"]] + if nic.find("source") is not None + else None, + "model": nic.find("model").attrib["type"] + if nic.find("model") is not None + else None, } - return _filter_nic(nic1) == _filter_nic(nic2) + + def _get_mac(nic): + return ( + nic.find("mac").attrib["address"].lower() + if nic.find("mac") is not None + else None + ) + + mac1 = _get_mac(nic1) + mac2 = _get_mac(nic2) + macs_equal = not mac1 or not mac2 or mac1 == mac2 + return _filter_nic(nic1) == _filter_nic(nic2) and macs_equal def _graphics_equal(gfx1, gfx2): - ''' + """ Test if two graphics devices should be considered the same device - ''' + """ + def _filter_graphics(gfx): - ''' + """ When the domain is running, the graphics element may contain additional properties with the default values. This function will strip down the default values. - ''' + """ gfx_copy = copy.deepcopy(gfx) - defaults = [{'node': '.', 'attrib': 'port', 'values': ['5900', '-1']}, - {'node': '.', 'attrib': 'address', 'values': ['127.0.0.1']}, - {'node': 'listen', 'attrib': 'address', 'values': ['127.0.0.1']}] + defaults = [ + {"node": ".", "attrib": "port", "values": ["5900", "-1"]}, + {"node": ".", "attrib": "address", "values": ["127.0.0.1"]}, + {"node": "listen", "attrib": "address", "values": ["127.0.0.1"]}, + ] for default in defaults: - node = gfx_copy.find(default['node']) - attrib = default['attrib'] - if node is not None and (attrib not in node.attrib or node.attrib[attrib] in default['values']): - node.set(attrib, default['values'][0]) + node = gfx_copy.find(default["node"]) + attrib = default["attrib"] + if node is not None and ( + attrib in node.attrib and node.attrib[attrib] in default["values"] + ): + node.attrib.pop(attrib) return gfx_copy - return ElementTree.tostring(_filter_graphics(gfx1)) == ElementTree.tostring(_filter_graphics(gfx2)) + return xmlutil.to_dict(_filter_graphics(gfx1), True) == xmlutil.to_dict( + _filter_graphics(gfx2), True + ) def _diff_lists(old, new, comparator): - ''' + """ Compare lists to extract the changes :param old: old list @@ -1693,99 +2090,111 @@ def _diff_lists(old, new, comparator): The sorted list is the union of unchanged and new lists, but keeping the original order from the new list. - ''' + """ + def _remove_indent(node): - ''' + """ Remove the XML indentation to compare XML trees more easily - ''' + """ node_copy = copy.deepcopy(node) node_copy.text = None for item in node_copy.iter(): item.tail = None return node_copy - diff = {'unchanged': [], 'new': [], 'deleted': [], 'sorted': []} + diff = {"unchanged": [], "new": [], "deleted": [], "sorted": []} # We don't want to alter old since it may be used later by caller old_devices = copy.deepcopy(old) for new_item in new: - found = [item for item in old_devices if comparator(_remove_indent(item), _remove_indent(new_item))] + found = [ + item + for item in old_devices + if comparator(_remove_indent(item), _remove_indent(new_item)) + ] if found: old_devices.remove(found[0]) - diff['unchanged'].append(found[0]) - diff['sorted'].append(found[0]) + diff["unchanged"].append(found[0]) + diff["sorted"].append(found[0]) else: - diff['new'].append(new_item) - diff['sorted'].append(new_item) - diff['deleted'] = old_devices + diff["new"].append(new_item) + diff["sorted"].append(new_item) + diff["deleted"] = old_devices return diff +def _get_disk_target(targets, disks_count, prefix): + """ + Compute the disk target name for a given prefix. + + :param targets: the list of already computed targets + :param disks: the number of disks + :param prefix: the prefix of the target name, i.e. "hd" + """ + return [ + "{0}{1}".format(prefix, string.ascii_lowercase[i]) + for i in range(disks_count) + if "{0}{1}".format(prefix, string.ascii_lowercase[i]) not in targets + ][0] + + def _diff_disk_lists(old, new): - ''' + """ Compare disk definitions to extract the changes and fix target devices :param old: list of ElementTree nodes representing the old disks :param new: list of ElementTree nodes representing the new disks - ''' + """ # Change the target device to avoid duplicates before diffing: this may lead # to additional changes. Think of unchanged disk 'hda' and another disk listed # before it becoming 'hda' too... the unchanged need to turn into 'hdb'. targets = [] - prefixes = ['fd', 'hd', 'vd', 'sd', 'xvd', 'ubd'] + prefixes = ["fd", "hd", "vd", "sd", "xvd", "ubd"] for disk in new: - target_node = disk.find('target') - target = target_node.get('dev') + target_node = disk.find("target") + target = target_node.get("dev") prefix = [item for item in prefixes if target.startswith(item)][0] - new_target = ['{0}{1}'.format(prefix, string.ascii_lowercase[i]) for i in range(len(new)) - if '{0}{1}'.format(prefix, string.ascii_lowercase[i]) not in targets][0] - target_node.set('dev', new_target) + new_target = _get_disk_target(targets, len(new), prefix) + target_node.set("dev", new_target) targets.append(new_target) return _diff_lists(old, new, _disks_equal) def _diff_interface_lists(old, new): - ''' + """ Compare network interface definitions to extract the changes :param old: list of ElementTree nodes representing the old interfaces :param new: list of ElementTree nodes representing the new interfaces - ''' - diff = _diff_lists(old, new, _nics_equal) - - # Remove duplicated addresses mac addresses and let libvirt generate them for us - macs = [nic.find('mac').get('address') for nic in diff['unchanged']] - for nic in diff['new']: - mac = nic.find('mac') - if mac.get('address') in macs: - nic.remove(mac) - - return diff + """ + return _diff_lists(old, new, _nics_equal) def _diff_graphics_lists(old, new): - ''' + """ Compare graphic devices definitions to extract the changes :param old: list of ElementTree nodes representing the old graphic devices :param new: list of ElementTree nodes representing the new graphic devices - ''' + """ return _diff_lists(old, new, _graphics_equal) -def update(name, - cpu=0, - mem=0, - disk_profile=None, - disks=None, - nic_profile=None, - interfaces=None, - graphics=None, - live=True, - boot=None, - test=False, - **kwargs): - ''' +def update( + name, + cpu=0, + mem=0, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + boot=None, + test=False, + **kwargs +): + """ Update the definition of an existing domain. :param name: Name of the domain to update @@ -1820,19 +2229,13 @@ def update(name, :param password: password to connect with, overriding defaults :param boot: - Specifies kernel for the virtual machine, as well as boot parameters - for the virtual machine. This is an optionl parameter, and all of the - keys are optional within the dictionary. If a remote path is provided - to kernel or initrd, salt will handle the downloading of the specified - remote fild, and will modify the XML accordingly. + Specifies kernel, initial ramdisk and kernel command line parameters for the virtual machine. + This is an optional parameter, all of the keys are optional within the dictionary. - .. code-block:: python + Refer to :ref:`init-boot-def` for the complete boot parameter description. - { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' - } + To update any boot parameters, specify the new path for each. To remove any boot parameters, + pass a None object, for instance: 'kernel': ``None``. .. versionadded:: 3000 @@ -1866,11 +2269,11 @@ def update(name, salt '*' virt.update domain cpu=2 mem=1024 - ''' + """ status = { - 'definition': False, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} + "definition": False, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, } conn = __get_conn(**kwargs) domain = _get_domain(conn, name) @@ -1878,56 +2281,68 @@ def update(name, need_update = False # Compute the XML to get the disks, interfaces and graphics - hypervisor = desc.get('type') - all_disks = _disk_profile(disk_profile, hypervisor, disks, name, **kwargs) + hypervisor = desc.get("type") + all_disks = _disk_profile(conn, disk_profile, hypervisor, disks, name) if boot is not None: boot = _handle_remote_boot_params(boot) - new_desc = ElementTree.fromstring(_gen_xml(name, - cpu or 0, - mem or 0, - all_disks, - _get_merged_nics(hypervisor, nic_profile, interfaces), - hypervisor, - domain.OSType(), - desc.find('.//os/type').get('arch'), - graphics, - boot, - **kwargs)) + new_desc = ElementTree.fromstring( + _gen_xml( + conn, + name, + cpu or 0, + mem or 0, + all_disks, + _get_merged_nics(hypervisor, nic_profile, interfaces), + hypervisor, + domain.OSType(), + desc.find(".//os/type").get("arch"), + graphics, + boot, + **kwargs + ) + ) # Update the cpu - cpu_node = desc.find('vcpu') + cpu_node = desc.find("vcpu") if cpu and int(cpu_node.text) != cpu: cpu_node.text = six.text_type(cpu) - cpu_node.set('current', six.text_type(cpu)) + cpu_node.set("current", six.text_type(cpu)) need_update = True # Update the kernel boot parameters - boot_tags = ['kernel', 'initrd', 'cmdline'] - parent_tag = desc.find('os') + boot_tags = ["kernel", "initrd", "cmdline", "loader", "nvram"] + parent_tag = desc.find("os") # We need to search for each possible subelement, and update it. for tag in boot_tags: # The Existing Tag... - found_tag = desc.find(tag) + found_tag = parent_tag.find(tag) # The new value boot_tag_value = boot.get(tag, None) if boot else None # Existing tag is found and values don't match - if found_tag and found_tag.text != boot_tag_value: + if found_tag is not None and found_tag.text != boot_tag_value: # If the existing tag is found, but the new value is None # remove it. If the existing tag is found, and the new value # doesn't match update it. In either case, mark for update. - if boot_tag_value is None \ - and boot is not None \ - and parent_tag is not None: - ElementTree.remove(parent_tag, tag) + if boot_tag_value is None and boot is not None and parent_tag is not None: + parent_tag.remove(found_tag) else: found_tag.text = boot_tag_value + # If the existing tag is loader or nvram, we need to update the corresponding attribute + if found_tag.tag == "loader" and boot_tag_value is not None: + found_tag.set("readonly", "yes") + found_tag.set("type", "pflash") + + if found_tag.tag == "nvram" and boot_tag_value is not None: + found_tag.set("template", found_tag.text) + found_tag.text = None + need_update = True # Existing tag is not found, but value is not None @@ -1939,52 +2354,79 @@ def update(name, if parent_tag is not None: child_tag = ElementTree.SubElement(parent_tag, tag) else: - new_parent_tag = ElementTree.Element('os') + new_parent_tag = ElementTree.Element("os") child_tag = ElementTree.SubElement(new_parent_tag, tag) child_tag.text = boot_tag_value + + # If the newly created tag is loader or nvram, we need to update the corresponding attribute + if child_tag.tag == "loader": + child_tag.set("readonly", "yes") + child_tag.set("type", "pflash") + + if child_tag.tag == "nvram": + child_tag.set("template", child_tag.text) + child_tag.text = None + need_update = True # Update the memory, note that libvirt outputs all memory sizes in KiB - for mem_node_name in ['memory', 'currentMemory']: + for mem_node_name in ["memory", "currentMemory"]: mem_node = desc.find(mem_node_name) if mem and int(mem_node.text) != mem * 1024: mem_node.text = six.text_type(mem) - mem_node.set('unit', 'MiB') + mem_node.set("unit", "MiB") need_update = True # Update the XML definition with the new disks and diff changes - devices_node = desc.find('devices') - parameters = {'disk': ['disks', 'disk_profile'], - 'interface': ['interfaces', 'nic_profile'], - 'graphics': ['graphics']} + devices_node = desc.find("devices") + parameters = { + "disk": ["disks", "disk_profile"], + "interface": ["interfaces", "nic_profile"], + "graphics": ["graphics"], + } changes = {} for dev_type in parameters: changes[dev_type] = {} func_locals = locals() - if [param for param in parameters[dev_type] if func_locals.get(param, None) is not None]: + if [ + param + for param in parameters[dev_type] + if func_locals.get(param, None) is not None + ]: old = devices_node.findall(dev_type) - new = new_desc.findall('devices/{0}'.format(dev_type)) - changes[dev_type] = globals()['_diff_{0}_lists'.format(dev_type)](old, new) - if changes[dev_type]['deleted'] or changes[dev_type]['new']: + new = new_desc.findall("devices/{0}".format(dev_type)) + changes[dev_type] = globals()["_diff_{0}_lists".format(dev_type)](old, new) + if changes[dev_type]["deleted"] or changes[dev_type]["new"]: for item in old: devices_node.remove(item) - devices_node.extend(changes[dev_type]['sorted']) + devices_node.extend(changes[dev_type]["sorted"]) need_update = True # Set the new definition if need_update: # Create missing disks if needed - if changes['disk']: - for idx, item in enumerate(changes['disk']['sorted']): - source_file = all_disks[idx]['source_file'] - if item in changes['disk']['new'] and source_file and not os.path.isfile(source_file) and not test: - _qemu_image_create(all_disks[idx]) - try: + if changes["disk"]: + for idx, item in enumerate(changes["disk"]["sorted"]): + source_file = all_disks[idx].get("source_file") + # We don't want to create image disks for cdrom devices + if all_disks[idx].get("device", "disk") == "cdrom": + continue + if ( + item in changes["disk"]["new"] + and source_file + and not os.path.isfile(source_file) + ): + _qemu_image_create(all_disks[idx]) + elif item in changes["disk"]["new"] and not source_file: + _disk_volume_create(conn, all_disks[idx]) + if not test: - conn.defineXML(salt.utils.stringutils.to_str(ElementTree.tostring(desc))) - status['definition'] = True + conn.defineXML( + salt.utils.stringutils.to_str(ElementTree.tostring(desc)) + ) + status["definition"] = True except libvirt.libvirtError as err: conn.close() raise err @@ -1993,48 +2435,133 @@ def update(name, # From that point on, failures are not blocking to try to live update as much # as possible. commands = [] + removable_changes = [] if domain.isActive() and live: if cpu: - commands.append({'device': 'cpu', - 'cmd': 'setVcpusFlags', - 'args': [cpu, libvirt.VIR_DOMAIN_AFFECT_LIVE]}) + commands.append( + { + "device": "cpu", + "cmd": "setVcpusFlags", + "args": [cpu, libvirt.VIR_DOMAIN_AFFECT_LIVE], + } + ) if mem: - commands.append({'device': 'mem', - 'cmd': 'setMemoryFlags', - 'args': [mem * 1024, libvirt.VIR_DOMAIN_AFFECT_LIVE]}) + commands.append( + { + "device": "mem", + "cmd": "setMemoryFlags", + "args": [mem * 1024, libvirt.VIR_DOMAIN_AFFECT_LIVE], + } + ) - for dev_type in ['disk', 'interface']: - for added in changes[dev_type].get('new', []): - commands.append({'device': dev_type, - 'cmd': 'attachDevice', - 'args': [salt.utils.stringutils.to_str(ElementTree.tostring(added))]}) + # Look for removable device source changes + new_disks = [] + for new_disk in changes["disk"].get("new", []): + device = new_disk.get("device", "disk") + if device not in ["cdrom", "floppy"]: + new_disks.append(new_disk) + continue + + target_dev = new_disk.find("target").get("dev") + matching = [ + old_disk + for old_disk in changes["disk"].get("deleted", []) + if old_disk.get("device", "disk") == device + and old_disk.find("target").get("dev") == target_dev + ] + if not matching: + new_disks.append(new_disk) + else: + # libvirt needs to keep the XML exactly as it was before + updated_disk = matching[0] + changes["disk"]["deleted"].remove(updated_disk) + removable_changes.append(updated_disk) + source_node = updated_disk.find("source") + new_source_node = new_disk.find("source") + source_file = ( + new_source_node.get("file") + if new_source_node is not None + else None + ) + + updated_disk.set("type", "file") + # Detaching device + if source_node is not None: + updated_disk.remove(source_node) + + # Attaching device + if source_file: + ElementTree.SubElement( + updated_disk, "source", attrib={"file": source_file} + ) + + changes["disk"]["new"] = new_disks + + for dev_type in ["disk", "interface"]: + for added in changes[dev_type].get("new", []): + commands.append( + { + "device": dev_type, + "cmd": "attachDevice", + "args": [ + salt.utils.stringutils.to_str( + ElementTree.tostring(added) + ) + ], + } + ) + + for removed in changes[dev_type].get("deleted", []): + commands.append( + { + "device": dev_type, + "cmd": "detachDevice", + "args": [ + salt.utils.stringutils.to_str( + ElementTree.tostring(removed) + ) + ], + } + ) - for removed in changes[dev_type].get('deleted', []): - commands.append({'device': dev_type, - 'cmd': 'detachDevice', - 'args': [salt.utils.stringutils.to_str(ElementTree.tostring(removed))]}) + for updated_disk in removable_changes: + commands.append( + { + "device": "disk", + "cmd": "updateDeviceFlags", + "args": [ + salt.utils.stringutils.to_str( + ElementTree.tostring(updated_disk) + ) + ], + } + ) for cmd in commands: try: - ret = getattr(domain, cmd['cmd'])(*cmd['args']) if not test else 0 - device_type = cmd['device'] - if device_type in ['cpu', 'mem']: + ret = getattr(domain, cmd["cmd"])(*cmd["args"]) if not test else 0 + device_type = cmd["device"] + if device_type in ["cpu", "mem"]: status[device_type] = not bool(ret) else: - actions = {'attachDevice': 'attached', 'detachDevice': 'detached'} - status[device_type][actions[cmd['cmd']]].append(cmd['args'][0]) + actions = { + "attachDevice": "attached", + "detachDevice": "detached", + "updateDeviceFlags": "updated", + } + status[device_type][actions[cmd["cmd"]]].append(cmd["args"][0]) except libvirt.libvirtError as err: - if 'errors' not in status: - status['errors'] = [] - status['errors'].append(six.text_type(err)) + if "errors" not in status: + status["errors"] = [] + status["errors"].append(six.text_type(err)) conn.close() return status def list_domains(**kwargs): - ''' + """ Return a list of available domains. :param connection: libvirt connection URI, overriding defaults @@ -2052,7 +2579,7 @@ def list_domains(**kwargs): .. code-block:: bash salt '*' virt.list_domains - ''' + """ vms = [] conn = __get_conn(**kwargs) for dom in _get_domain(conn, iterable=True): @@ -2062,7 +2589,7 @@ def list_domains(**kwargs): def list_active_vms(**kwargs): - ''' + """ Return a list of names for active virtual machine on the minion :param connection: libvirt connection URI, overriding defaults @@ -2080,7 +2607,7 @@ def list_active_vms(**kwargs): .. code-block:: bash salt '*' virt.list_active_vms - ''' + """ vms = [] conn = __get_conn(**kwargs) for dom in _get_domain(conn, iterable=True, inactive=False): @@ -2090,7 +2617,7 @@ def list_active_vms(**kwargs): def list_inactive_vms(**kwargs): - ''' + """ Return a list of names for inactive virtual machine on the minion :param connection: libvirt connection URI, overriding defaults @@ -2108,7 +2635,7 @@ def list_inactive_vms(**kwargs): .. code-block:: bash salt '*' virt.list_inactive_vms - ''' + """ vms = [] conn = __get_conn(**kwargs) for dom in _get_domain(conn, iterable=True, active=False): @@ -2118,7 +2645,7 @@ def list_inactive_vms(**kwargs): def vm_info(vm_=None, **kwargs): - ''' + """ Return detailed information about the vms on this hyper in a list of dicts: @@ -2153,38 +2680,43 @@ def vm_info(vm_=None, **kwargs): .. code-block:: bash - salt '*' virt.vm_info - ''' - def _info(dom): - ''' + salt '*' virt.vm_info + """ + + def _info(conn, dom): + """ Compute the infos of a domain - ''' + """ raw = dom.info() - return {'cpu': raw[3], - 'cputime': int(raw[4]), - 'disks': _get_disks(dom), - 'graphics': _get_graphics(dom), - 'nics': _get_nics(dom), - 'uuid': _get_uuid(dom), - 'on_crash': _get_on_crash(dom), - 'on_reboot': _get_on_reboot(dom), - 'on_poweroff': _get_on_poweroff(dom), - 'maxMem': int(raw[1]), - 'mem': int(raw[2]), - 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')} + return { + "cpu": raw[3], + "cputime": int(raw[4]), + "disks": _get_disks(conn, dom), + "graphics": _get_graphics(dom), + "nics": _get_nics(dom), + "uuid": _get_uuid(dom), + "loader": _get_loader(dom), + "on_crash": _get_on_crash(dom), + "on_reboot": _get_on_reboot(dom), + "on_poweroff": _get_on_poweroff(dom), + "maxMem": int(raw[1]), + "mem": int(raw[2]), + "state": VIRT_STATE_NAME_MAP.get(raw[0], "unknown"), + } + info = {} conn = __get_conn(**kwargs) if vm_: - info[vm_] = _info(_get_domain(conn, vm_)) + info[vm_] = _info(conn, _get_domain(conn, vm_)) else: for domain in _get_domain(conn, iterable=True): - info[domain.name()] = _info(domain) + info[domain.name()] = _info(conn, domain) conn.close() return info def vm_state(vm_=None, **kwargs): - ''' + """ Return list of all the vms and their state. If you pass a VM name in as an argument then it will return info @@ -2206,15 +2738,17 @@ def vm_state(vm_=None, **kwargs): .. code-block:: bash salt '*' virt.vm_state <domain> - ''' + """ + def _info(dom): - ''' + """ Compute domain state - ''' - state = '' + """ + state = "" raw = dom.info() - state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown') + state = VIRT_STATE_NAME_MAP.get(raw[0], "unknown") return state + info = {} conn = __get_conn(**kwargs) if vm_: @@ -2227,23 +2761,25 @@ def vm_state(vm_=None, **kwargs): def _node_info(conn): - ''' + """ Internal variant of node_info taking a libvirt connection as parameter - ''' + """ raw = conn.getInfo() - info = {'cpucores': raw[6], - 'cpumhz': raw[3], - 'cpumodel': six.text_type(raw[0]), - 'cpus': raw[2], - 'cputhreads': raw[7], - 'numanodes': raw[4], - 'phymemory': raw[1], - 'sockets': raw[5]} + info = { + "cpucores": raw[6], + "cpumhz": raw[3], + "cpumodel": six.text_type(raw[0]), + "cpus": raw[2], + "cputhreads": raw[7], + "numanodes": raw[4], + "phymemory": raw[1], + "sockets": raw[5], + } return info def node_info(**kwargs): - ''' + """ Return a dict with information about this node :param connection: libvirt connection URI, overriding defaults @@ -2261,7 +2797,7 @@ def node_info(**kwargs): .. code-block:: bash salt '*' virt.node_info - ''' + """ conn = __get_conn(**kwargs) info = _node_info(conn) conn.close() @@ -2269,7 +2805,7 @@ def node_info(**kwargs): def get_nics(vm_, **kwargs): - ''' + """ Return info about the network interfaces of a named vm :param vm_: name of the domain @@ -2288,7 +2824,7 @@ def get_nics(vm_, **kwargs): .. code-block:: bash salt '*' virt.get_nics <domain> - ''' + """ conn = __get_conn(**kwargs) nics = _get_nics(_get_domain(conn, vm_)) conn.close() @@ -2296,7 +2832,7 @@ def get_nics(vm_, **kwargs): def get_macs(vm_, **kwargs): - ''' + """ Return a list off MAC addresses from the named vm :param vm_: name of the domain @@ -2315,13 +2851,13 @@ def get_macs(vm_, **kwargs): .. code-block:: bash salt '*' virt.get_macs <domain> - ''' + """ doc = ElementTree.fromstring(get_xml(vm_, **kwargs)) - return [node.get('address') for node in doc.findall('devices/interface/mac')] + return [node.get("address") for node in doc.findall("devices/interface/mac")] def get_graphics(vm_, **kwargs): - ''' + """ Returns the information on vnc for a given vm :param vm_: name of the domain @@ -2340,15 +2876,40 @@ def get_graphics(vm_, **kwargs): .. code-block:: bash salt '*' virt.get_graphics <domain> - ''' + """ conn = __get_conn(**kwargs) graphics = _get_graphics(_get_domain(conn, vm_)) conn.close() return graphics +def get_loader(vm_, **kwargs): + """ + Returns the information on the loader for a given vm + + :param vm_: name of the domain + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + CLI Example: + + .. code-block:: bash + + salt '*' virt.get_loader <domain> + + .. versionadded:: Fluorine + """ + conn = __get_conn(**kwargs) + try: + loader = _get_loader(_get_domain(conn, vm_)) + return loader + finally: + conn.close() + + def get_disks(vm_, **kwargs): - ''' + """ Return the disks of a named vm :param vm_: name of the domain @@ -2367,15 +2928,15 @@ def get_disks(vm_, **kwargs): .. code-block:: bash salt '*' virt.get_disks <domain> - ''' + """ conn = __get_conn(**kwargs) - disks = _get_disks(_get_domain(conn, vm_)) + disks = _get_disks(conn, _get_domain(conn, vm_)) conn.close() return disks def setmem(vm_, memory, config=False, **kwargs): - ''' + """ Changes the amount of memory allocated to VM. The VM must be shutdown for this to work. @@ -2398,11 +2959,11 @@ def setmem(vm_, memory, config=False, **kwargs): salt '*' virt.setmem <domain> <size> salt '*' virt.setmem my_domain 768 - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) - if VIRT_STATE_NAME_MAP.get(dom.info()[0], 'unknown') != 'shutdown': + if VIRT_STATE_NAME_MAP.get(dom.info()[0], "unknown") != "shutdown": return False # libvirt has a funny bitwise system for the flags in that the flag @@ -2422,7 +2983,7 @@ def setmem(vm_, memory, config=False, **kwargs): def setvcpus(vm_, vcpus, config=False, **kwargs): - ''' + """ Changes the amount of vcpus allocated to VM. The VM must be shutdown for this to work. @@ -2447,11 +3008,11 @@ def setvcpus(vm_, vcpus, config=False, **kwargs): salt '*' virt.setvcpus <domain> <amount> salt '*' virt.setvcpus my_domain 4 - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) - if VIRT_STATE_NAME_MAP.get(dom.info()[0], 'unknown') != 'shutdown': + if VIRT_STATE_NAME_MAP.get(dom.info()[0], "unknown") != "shutdown": return False # see notes in setmem @@ -2468,9 +3029,9 @@ def setvcpus(vm_, vcpus, config=False, **kwargs): def _freemem(conn): - ''' + """ Internal variant of freemem taking a libvirt connection as parameter - ''' + """ mem = conn.getInfo()[1] # Take off just enough to sustain the hypervisor mem -= 256 @@ -2481,7 +3042,7 @@ def _freemem(conn): def freemem(**kwargs): - ''' + """ Return an int representing the amount of memory (in MB) that has not been given to virtual machines on this node @@ -2500,7 +3061,7 @@ def freemem(**kwargs): .. code-block:: bash salt '*' virt.freemem - ''' + """ conn = __get_conn(**kwargs) mem = _freemem(conn) conn.close() @@ -2508,9 +3069,9 @@ def freemem(**kwargs): def _freecpu(conn): - ''' + """ Internal variant of freecpu taking a libvirt connection as parameter - ''' + """ cpus = conn.getInfo()[2] for dom in _get_domain(conn, iterable=True): if dom.ID() > 0: @@ -2519,7 +3080,7 @@ def _freecpu(conn): def freecpu(**kwargs): - ''' + """ Return an int representing the number of unallocated cpus on this hypervisor @@ -2538,7 +3099,7 @@ def freecpu(**kwargs): .. code-block:: bash salt '*' virt.freecpu - ''' + """ conn = __get_conn(**kwargs) cpus = _freecpu(conn) conn.close() @@ -2546,7 +3107,7 @@ def freecpu(**kwargs): def full_info(**kwargs): - ''' + """ Return the node_info, vm_info and freemem :param connection: libvirt connection URI, overriding defaults @@ -2564,18 +3125,20 @@ def full_info(**kwargs): .. code-block:: bash salt '*' virt.full_info - ''' + """ conn = __get_conn(**kwargs) - info = {'freecpu': _freecpu(conn), - 'freemem': _freemem(conn), - 'node_info': _node_info(conn), - 'vm_info': vm_info()} + info = { + "freecpu": _freecpu(conn), + "freemem": _freemem(conn), + "node_info": _node_info(conn), + "vm_info": vm_info(), + } conn.close() return info def get_xml(vm_, **kwargs): - ''' + """ Returns the XML for a given vm :param vm_: domain name @@ -2594,17 +3157,19 @@ def get_xml(vm_, **kwargs): .. code-block:: bash salt '*' virt.get_xml <domain> - ''' + """ conn = __get_conn(**kwargs) - xml_desc = vm_.XMLDesc(0) if isinstance( - vm_, libvirt.virDomain - ) else _get_domain(conn, vm_).XMLDesc(0) + xml_desc = ( + vm_.XMLDesc(0) + if isinstance(vm_, libvirt.virDomain) + else _get_domain(conn, vm_).XMLDesc(0) + ) conn.close() return xml_desc def get_profiles(hypervisor=None, **kwargs): - ''' + """ Return the virt profiles for hypervisor. Currently there are profiles for: @@ -2629,40 +3194,42 @@ def get_profiles(hypervisor=None, **kwargs): salt '*' virt.get_profiles salt '*' virt.get_profiles hypervisor=esxi - ''' + """ ret = {} + # Use the machine types as possible values + # Prefer 'kvm' over the others if available caps = capabilities(**kwargs) - hypervisors = sorted({x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y}) - default_hypervisor = 'kvm' if 'kvm' in hypervisors else hypervisors[0] + hypervisors = sorted( + { + x + for y in [guest["arch"]["domains"].keys() for guest in caps["guests"]] + for x in y + } + ) + default_hypervisor = "kvm" if "kvm" in hypervisors else hypervisors[0] if not hypervisor: - hypervisor = __salt__['config.get']('libvirt:hypervisor') - if hypervisor is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'libvirt:hypervisor\' configuration property has been deprecated. ' - 'Rather use the \'virt:connection:uri\' to properly define the libvirt ' - 'URI or alias of the host to connect to. \'libvirt:hypervisor\' will ' - 'stop being used in {version}.' + hypervisor = default_hypervisor + virtconf = __salt__["config.get"]("virt", {}) + for typ in ["disk", "nic"]: + _func = getattr(sys.modules[__name__], "_{0}_profile".format(typ)) + ret[typ] = { + "default": _func( + "default", hypervisor if hypervisor else default_hypervisor ) - else: - # Use the machine types as possible values - # Prefer 'kvm' over the others if available - hypervisor = default_hypervisor - virtconf = __salt__['config.get']('virt', {}) - for typ in ['disk', 'nic']: - _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ)) - ret[typ] = {'default': _func('default', hypervisor)} + } if typ in virtconf: ret.setdefault(typ, {}) for prf in virtconf[typ]: - ret[typ][prf] = _func(prf, hypervisor) + ret[typ][prf] = _func( + prf, hypervisor if hypervisor else default_hypervisor + ) return ret def shutdown(vm_, **kwargs): - ''' + """ Send a soft shutdown signal to the named vm :param vm_: domain name @@ -2681,7 +3248,7 @@ def shutdown(vm_, **kwargs): .. code-block:: bash salt '*' virt.shutdown <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) ret = dom.shutdown() == 0 @@ -2690,7 +3257,7 @@ def shutdown(vm_, **kwargs): def pause(vm_, **kwargs): - ''' + """ Pause the named vm :param vm_: domain name @@ -2709,7 +3276,7 @@ def pause(vm_, **kwargs): .. code-block:: bash salt '*' virt.pause <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) ret = dom.suspend() == 0 @@ -2718,7 +3285,7 @@ def pause(vm_, **kwargs): def resume(vm_, **kwargs): - ''' + """ Resume the named vm :param vm_: domain name @@ -2737,7 +3304,7 @@ def resume(vm_, **kwargs): .. code-block:: bash salt '*' virt.resume <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) ret = dom.resume() == 0 @@ -2746,7 +3313,7 @@ def resume(vm_, **kwargs): def start(name, **kwargs): - ''' + """ Start a defined domain :param vm_: domain name @@ -2765,7 +3332,7 @@ def start(name, **kwargs): .. code-block:: bash salt '*' virt.start <domain> - ''' + """ conn = __get_conn(**kwargs) ret = _get_domain(conn, name).create() == 0 conn.close() @@ -2773,7 +3340,7 @@ def start(name, **kwargs): def stop(name, **kwargs): - ''' + """ Hard power down the virtual machine, this is equivalent to pulling the power. :param vm_: domain name @@ -2792,7 +3359,7 @@ def stop(name, **kwargs): .. code-block:: bash salt '*' virt.stop <domain> - ''' + """ conn = __get_conn(**kwargs) ret = _get_domain(conn, name).destroy() == 0 conn.close() @@ -2800,7 +3367,7 @@ def stop(name, **kwargs): def reboot(name, **kwargs): - ''' + """ Reboot a domain via ACPI request :param vm_: domain name @@ -2819,7 +3386,7 @@ def reboot(name, **kwargs): .. code-block:: bash salt '*' virt.reboot <domain> - ''' + """ conn = __get_conn(**kwargs) ret = _get_domain(conn, name).reboot(libvirt.VIR_DOMAIN_REBOOT_DEFAULT) == 0 conn.close() @@ -2827,7 +3394,7 @@ def reboot(name, **kwargs): def reset(vm_, **kwargs): - ''' + """ Reset a VM by emulating the reset button on a physical machine :param vm_: domain name @@ -2846,7 +3413,7 @@ def reset(vm_, **kwargs): .. code-block:: bash salt '*' virt.reset <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) @@ -2859,7 +3426,7 @@ def reset(vm_, **kwargs): def ctrl_alt_del(vm_, **kwargs): - ''' + """ Sends CTRL+ALT+DEL to a VM :param vm_: domain name @@ -2878,7 +3445,7 @@ def ctrl_alt_del(vm_, **kwargs): .. code-block:: bash salt '*' virt.ctrl_alt_del <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) ret = dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0 @@ -2887,7 +3454,7 @@ def ctrl_alt_del(vm_, **kwargs): def create_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name - ''' + """ Start a transient domain based on the XML passed to the function :param xml: libvirt XML definition of the domain @@ -2906,7 +3473,7 @@ def create_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name .. code-block:: bash salt '*' virt.create_xml_str <XML in string format> - ''' + """ conn = __get_conn(**kwargs) ret = conn.createXML(xml, 0) is not None conn.close() @@ -2914,7 +3481,7 @@ def create_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name def create_xml_path(path, **kwargs): - ''' + """ Start a transient domain based on the XML-file path passed to the function :param path: path to a file containing the libvirt XML definition of the domain @@ -2933,19 +3500,18 @@ def create_xml_path(path, **kwargs): .. code-block:: bash salt '*' virt.create_xml_path <path to XML file on the node> - ''' + """ try: - with salt.utils.files.fopen(path, 'r') as fp_: + with salt.utils.files.fopen(path, "r") as fp_: return create_xml_str( - salt.utils.stringutils.to_unicode(fp_.read()), - **kwargs + salt.utils.stringutils.to_unicode(fp_.read()), **kwargs ) except (OSError, IOError): return False def define_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name - ''' + """ Define a persistent domain based on the XML passed to the function :param xml: libvirt XML definition of the domain @@ -2964,7 +3530,7 @@ def define_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name .. code-block:: bash salt '*' virt.define_xml_str <XML in string format> - ''' + """ conn = __get_conn(**kwargs) ret = conn.defineXML(xml) is not None conn.close() @@ -2972,7 +3538,7 @@ def define_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name def define_xml_path(path, **kwargs): - ''' + """ Define a persistent domain based on the XML-file path passed to the function :param path: path to a file containing the libvirt XML definition of the domain @@ -2992,22 +3558,41 @@ def define_xml_path(path, **kwargs): salt '*' virt.define_xml_path <path to XML file on the node> - ''' + """ try: - with salt.utils.files.fopen(path, 'r') as fp_: + with salt.utils.files.fopen(path, "r") as fp_: return define_xml_str( - salt.utils.stringutils.to_unicode(fp_.read()), - **kwargs + salt.utils.stringutils.to_unicode(fp_.read()), **kwargs ) except (OSError, IOError): return False -def define_vol_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name - ''' +def _define_vol_xml_str(conn, xml, pool=None): # pylint: disable=redefined-outer-name + """ + Same function than define_vml_xml_str but using an already opened libvirt connection + """ + default_pool = "default" if conn.getType() != "ESX" else "0" + poolname = ( + pool if pool else __salt__["config.get"]("virt:storagepool", default_pool) + ) + pool = conn.storagePoolLookupByName(six.text_type(poolname)) + ret = pool.createXML(xml, 0) is not None + return ret + + +def define_vol_xml_str( + xml, pool=None, **kwargs +): # pylint: disable=redefined-outer-name + """ Define a volume based on the XML passed to the function :param xml: libvirt XML definition of the storage volume + :param pool: + storage pool name to define the volume in. + If defined, this parameter will override the configuration setting. + + .. versionadded:: Sodium :param connection: libvirt connection URI, overriding defaults .. versionadded:: 2019.2.0 @@ -3025,36 +3610,34 @@ def define_vol_xml_str(xml, **kwargs): # pylint: disable=redefined-outer-name salt '*' virt.define_vol_xml_str <XML in string format> The storage pool where the disk image will be defined is ``default`` - unless changed with a configuration like this: + unless changed with the pool parameter or a configuration like this: .. code-block:: yaml virt: storagepool: mine - ''' - poolname = __salt__['config.get']('libvirt:storagepool', None) - if poolname is not None: - salt.utils.versions.warn_until( - 'Sodium', - '\'libvirt:storagepool\' has been deprecated in favor of ' - '\'virt:storagepool\'. \'libvirt:storagepool\' will stop ' - 'being used in {version}.' - ) - else: - poolname = __salt__['config.get']('virt:storagepool', 'default') - + """ conn = __get_conn(**kwargs) - pool = conn.storagePoolLookupByName(six.text_type(poolname)) - ret = pool.createXML(xml, 0) is not None - conn.close() + ret = False + try: + ret = _define_vol_xml_str(conn, xml, pool=pool) + except libvirtError as err: + raise CommandExecutionError(err.get_error_message()) + finally: + conn.close() return ret -def define_vol_xml_path(path, **kwargs): - ''' +def define_vol_xml_path(path, pool=None, **kwargs): + """ Define a volume based on the XML-file path passed to the function :param path: path to a file containing the libvirt XML definition of the volume + :param pool: + storage pool name to define the volume in. + If defined, this parameter will override the configuration setting. + + .. versionadded:: Sodium :param connection: libvirt connection URI, overriding defaults .. versionadded:: 2019.2.0 @@ -3071,19 +3654,18 @@ def define_vol_xml_path(path, **kwargs): salt '*' virt.define_vol_xml_path <path to XML file on the node> - ''' + """ try: - with salt.utils.files.fopen(path, 'r') as fp_: + with salt.utils.files.fopen(path, "r") as fp_: return define_vol_xml_str( - salt.utils.stringutils.to_unicode(fp_.read()), - **kwargs + salt.utils.stringutils.to_unicode(fp_.read()), pool=pool, **kwargs ) except (OSError, IOError): return False def migrate_non_shared(vm_, target, ssh=False): - ''' + """ Attempt to execute non-shared storage "all" migration :param vm_: domain name @@ -3106,18 +3688,17 @@ def migrate_non_shared(vm_, target, ssh=False): For more details on tunnelled data migrations, report to https://libvirt.org/migration.html#transporttunnel - ''' - cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\ - + _get_target(target, ssh) + """ + cmd = ( + _get_migrate_command() + " --copy-storage-all " + vm_ + _get_target(target, ssh) + ) - stdout = subprocess.Popen(cmd, - shell=True, - stdout=subprocess.PIPE).communicate()[0] + stdout = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0] return salt.utils.stringutils.to_str(stdout) def migrate_non_shared_inc(vm_, target, ssh=False): - ''' + """ Attempt to execute non-shared storage "all" migration :param vm_: domain name @@ -3140,18 +3721,17 @@ def migrate_non_shared_inc(vm_, target, ssh=False): For more details on tunnelled data migrations, report to https://libvirt.org/migration.html#transporttunnel - ''' - cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\ - + _get_target(target, ssh) + """ + cmd = ( + _get_migrate_command() + " --copy-storage-inc " + vm_ + _get_target(target, ssh) + ) - stdout = subprocess.Popen(cmd, - shell=True, - stdout=subprocess.PIPE).communicate()[0] + stdout = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0] return salt.utils.stringutils.to_str(stdout) def migrate(vm_, target, ssh=False): - ''' + """ Shared storage migration :param vm_: domain name @@ -3174,18 +3754,15 @@ def migrate(vm_, target, ssh=False): For more details on tunnelled data migrations, report to https://libvirt.org/migration.html#transporttunnel - ''' - cmd = _get_migrate_command() + ' ' + vm_\ - + _get_target(target, ssh) + """ + cmd = _get_migrate_command() + " " + vm_ + _get_target(target, ssh) - stdout = subprocess.Popen(cmd, - shell=True, - stdout=subprocess.PIPE).communicate()[0] + stdout = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0] return salt.utils.stringutils.to_str(stdout) def seed_non_shared_migrate(disks, force=False): - ''' + """ Non shared migration requires that the disks be present on the migration destination, pass the disks information via this function, to the migration destination before executing the migration. @@ -3199,33 +3776,37 @@ def seed_non_shared_migrate(disks, force=False): .. code-block:: bash salt '*' virt.seed_non_shared_migrate <disks> - ''' + """ for _, data in six.iteritems(disks): - fn_ = data['file'] - form = data['file format'] - size = data['virtual size'].split()[1][1:] + fn_ = data["file"] + form = data["file format"] + size = data["virtual size"].split()[1][1:] if os.path.isfile(fn_) and not force: # the target exists, check to see if it is compatible - pre = salt.utils.yaml.safe_load(subprocess.Popen('qemu-img info arch', - shell=True, - stdout=subprocess.PIPE).communicate()[0]) - if pre['file format'] != data['file format']\ - and pre['virtual size'] != data['virtual size']: + pre = salt.utils.yaml.safe_load( + subprocess.Popen( + "qemu-img info arch", shell=True, stdout=subprocess.PIPE + ).communicate()[0] + ) + if ( + pre["file format"] != data["file format"] + and pre["virtual size"] != data["virtual size"] + ): return False if not os.path.isdir(os.path.dirname(fn_)): os.makedirs(os.path.dirname(fn_)) if os.path.isfile(fn_): os.remove(fn_) - cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size + cmd = "qemu-img create -f " + form + " " + fn_ + " " + size subprocess.call(cmd, shell=True) creds = _libvirt_creds() - cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_ + cmd = "chown " + creds["user"] + ":" + creds["group"] + " " + fn_ subprocess.call(cmd, shell=True) return True -def set_autostart(vm_, state='on', **kwargs): - ''' +def set_autostart(vm_, state="on", **kwargs): + """ Set the autostart flag on a VM so that the VM will start with the host system on reboot. @@ -3247,17 +3828,17 @@ def set_autostart(vm_, state='on', **kwargs): .. code-block:: bash salt "*" virt.set_autostart <domain> <on | off> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) # return False if state is set to something other then on or off ret = False - if state == 'on': + if state == "on": ret = dom.setAutostart(1) == 0 - elif state == 'off': + elif state == "off": ret = dom.setAutostart(0) == 0 conn.close() @@ -3265,7 +3846,7 @@ def set_autostart(vm_, state='on', **kwargs): def undefine(vm_, **kwargs): - ''' + """ Remove a defined vm, this does not purge the virtual machine image, and this only works if the vm is powered down @@ -3285,10 +3866,10 @@ def undefine(vm_, **kwargs): .. code-block:: bash salt '*' virt.undefine <domain> - ''' + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) - if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False): + if getattr(libvirt, "VIR_DOMAIN_UNDEFINE_NVRAM", False): # This one is only in 1.2.8+ ret = dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0 else: @@ -3297,16 +3878,12 @@ def undefine(vm_, **kwargs): return ret -def purge(vm_, dirs=False, removables=None, **kwargs): - ''' +def purge(vm_, dirs=False, removables=False, **kwargs): + """ Recursively destroy and delete a persistent virtual machine, pass True for dir's to also delete the directories containing the virtual machine disk images - USE WITH EXTREME CAUTION! - Pass removables=False to avoid deleting cdrom and floppy images. To avoid - disruption, the default but dangerous value is True. This will be changed - to the safer False default value in Sodium. - :param vm_: domain name :param dirs: pass True to remove containing directories :param removables: pass True to remove removable devices @@ -3326,31 +3903,46 @@ def purge(vm_, dirs=False, removables=None, **kwargs): .. code-block:: bash - salt '*' virt.purge <domain> removables=False - ''' + salt '*' virt.purge <domain> + """ conn = __get_conn(**kwargs) dom = _get_domain(conn, vm_) - disks = _get_disks(dom) - if removables is None: - salt.utils.versions.warn_until( - 'Sodium', - 'removables argument default value is True, but will be changed ' - 'to False by default in {version}. Please set to True to maintain ' - 'the current behavior in the future.' - ) - removables = True - if VIRT_STATE_NAME_MAP.get(dom.info()[0], 'unknown') != 'shutdown' and dom.destroy() != 0: + disks = _get_disks(conn, dom) + if ( + VIRT_STATE_NAME_MAP.get(dom.info()[0], "unknown") != "shutdown" + and dom.destroy() != 0 + ): return False directories = set() for disk in disks: - if not removables and disks[disk]['type'] in ['cdrom', 'floppy']: + if not removables and disks[disk]["type"] in ["cdrom", "floppy"]: continue - os.remove(disks[disk]['file']) - directories.add(os.path.dirname(disks[disk]['file'])) + if disks[disk].get("zfs", False): + # TODO create solution for 'dataset is busy' + time.sleep(3) + fs_name = disks[disk]["file"][len("/dev/zvol/") :] + log.info("Destroying VM ZFS volume {0}".format(fs_name)) + __salt__["zfs.destroy"](name=fs_name, force=True) + elif os.path.exists(disks[disk]["file"]): + os.remove(disks[disk]["file"]) + directories.add(os.path.dirname(disks[disk]["file"])) + else: + # We may have a volume to delete here + matcher = re.match("^(?P<pool>[^/]+)/(?P<volume>.*)$", disks[disk]["file"],) + if matcher: + pool_name = matcher.group("pool") + pool = None + if pool_name in conn.listStoragePools(): + pool = conn.storagePoolLookupByName(pool_name) + + if pool and matcher.group("volume") in pool.listVolumes(): + volume = pool.storageVolLookupByName(matcher.group("volume")) + volume.delete() + if dirs: for dir_ in directories: shutil.rmtree(dir_) - if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False): + if getattr(libvirt, "VIR_DOMAIN_UNDEFINE_NVRAM", False): # This one is only in 1.2.8+ try: dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) @@ -3363,7 +3955,7 @@ def purge(vm_, dirs=False, removables=None, **kwargs): def virt_type(): - ''' + """ Returns the virtual machine type as a string CLI Example: @@ -3371,92 +3963,53 @@ def virt_type(): .. code-block:: bash salt '*' virt.virt_type - ''' - return __grains__['virtual'] + """ + return __grains__["virtual"] def _is_kvm_hyper(): - ''' + """ Returns a bool whether or not this node is a KVM hypervisor - ''' + """ try: - with salt.utils.files.fopen('/proc/modules') as fp_: - if 'kvm_' not in salt.utils.stringutils.to_unicode(fp_.read()): + with salt.utils.files.fopen("/proc/modules") as fp_: + if "kvm_" not in salt.utils.stringutils.to_unicode(fp_.read()): return False except IOError: # No /proc/modules? Are we on Windows? Or Solaris? return False - return 'libvirtd' in __salt__['cmd.run'](__grains__['ps']) - - -def is_kvm_hyper(): - ''' - Returns a bool whether or not this node is a KVM hypervisor - - CLI Example: - - .. code-block:: bash - - salt '*' virt.is_kvm_hyper - - .. deprecated:: 2019.2.0 - ''' - salt.utils.versions.warn_until( - 'Sodium', - '\'is_kvm_hyper\' function has been deprecated. Use the \'get_hypervisor\' == "kvm" instead. ' - '\'is_kvm_hyper\' will be removed in {version}.' - ) - return _is_kvm_hyper() + return "libvirtd" in __salt__["cmd.run"](__grains__["ps"]) def _is_xen_hyper(): - ''' + """ Returns a bool whether or not this node is a XEN hypervisor - ''' + """ try: - if __grains__['virtual_subtype'] != 'Xen Dom0': + if __grains__["virtual_subtype"] != "Xen Dom0": return False except KeyError: # virtual_subtype isn't set everywhere. return False try: - with salt.utils.files.fopen('/proc/modules') as fp_: - if 'xen_' not in salt.utils.stringutils.to_unicode(fp_.read()): + with salt.utils.files.fopen("/proc/modules") as fp_: + if "xen_" not in salt.utils.stringutils.to_unicode(fp_.read()): return False except (OSError, IOError): # No /proc/modules? Are we on Windows? Or Solaris? return False - return 'libvirtd' in __salt__['cmd.run'](__grains__['ps']) - - -def is_xen_hyper(): - ''' - Returns a bool whether or not this node is a XEN hypervisor - - CLI Example: - - .. code-block:: bash - - salt '*' virt.is_xen_hyper - - .. deprecated:: 2019.2.0 - ''' - salt.utils.versions.warn_until( - 'Sodium', - '\'is_xen_hyper\' function has been deprecated. Use the \'get_hypervisor\' == "xen" instead. ' - '\'is_xen_hyper\' will be removed in {version}.' - ) - return _is_xen_hyper() + return "libvirtd" in __salt__["cmd.run"](__grains__["ps"]) def get_hypervisor(): - ''' + """ Returns the name of the hypervisor running on this node or ``None``. Detected hypervisors: - kvm - xen + - bhyve CLI Example: @@ -3465,17 +4018,34 @@ def get_hypervisor(): salt '*' virt.get_hypervisor .. versionadded:: 2019.2.0 - the function and the ``kvm`` and ``xen`` hypervisors support - ''' + the function and the ``kvm``, ``xen`` and ``bhyve`` hypervisors support + """ # To add a new 'foo' hypervisor, add the _is_foo_hyper function, # add 'foo' to the list below and add it to the docstring with a .. versionadded:: - hypervisors = ['kvm', 'xen'] - result = [hyper for hyper in hypervisors if getattr(sys.modules[__name__], '_is_{}_hyper'.format(hyper))()] + hypervisors = ["kvm", "xen", "bhyve"] + result = [ + hyper + for hyper in hypervisors + if getattr(sys.modules[__name__], "_is_{}_hyper".format(hyper))() + ] return result[0] if result else None +def _is_bhyve_hyper(): + sysctl_cmd = "sysctl hw.vmm.create" + vmm_enabled = False + try: + stdout = subprocess.Popen( + sysctl_cmd, shell=True, stdout=subprocess.PIPE + ).communicate()[0] + vmm_enabled = len(salt.utils.stringutils.to_str(stdout).split('"')[1]) != 0 + except IndexError: + pass + return vmm_enabled + + def is_hyper(): - ''' + """ Returns a bool whether or not this node is a hypervisor of any kind CLI Example: @@ -3483,14 +4053,14 @@ def is_hyper(): .. code-block:: bash salt '*' virt.is_hyper - ''' + """ if HAS_LIBVIRT: - return is_xen_hyper() or is_kvm_hyper() + return _is_xen_hyper() or _is_kvm_hyper() or _is_bhyve_hyper() return False def vm_cputime(vm_=None, **kwargs): - ''' + """ Return cputime used by the vms on this hyper in a list of dicts: @@ -3523,14 +4093,14 @@ def vm_cputime(vm_=None, **kwargs): .. code-block:: bash salt '*' virt.vm_cputime - ''' + """ conn = __get_conn(**kwargs) host_cpus = conn.getInfo()[2] def _info(dom): - ''' + """ Compute cputime info of a domain - ''' + """ raw = dom.info() vcpus = int(raw[3]) cputime = int(raw[4]) @@ -3539,9 +4109,10 @@ def vm_cputime(vm_=None, **kwargs): # Divide by vcpus to always return a number between 0 and 100 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus return { - 'cputime': int(raw[4]), - 'cputime_percent': int('{0:.0f}'.format(cputime_percent)) - } + "cputime": int(raw[4]), + "cputime_percent": int("{0:.0f}".format(cputime_percent)), + } + info = {} if vm_: info[vm_] = _info(_get_domain(conn, vm_)) @@ -3553,7 +4124,7 @@ def vm_cputime(vm_=None, **kwargs): def vm_netstats(vm_=None, **kwargs): - ''' + """ Return combined network counters used by the vms on this hyper in a list of dicts: @@ -3592,36 +4163,38 @@ def vm_netstats(vm_=None, **kwargs): .. code-block:: bash salt '*' virt.vm_netstats - ''' + """ + def _info(dom): - ''' + """ Compute network stats of a domain - ''' + """ nics = _get_nics(dom) ret = { - 'rx_bytes': 0, - 'rx_packets': 0, - 'rx_errs': 0, - 'rx_drop': 0, - 'tx_bytes': 0, - 'tx_packets': 0, - 'tx_errs': 0, - 'tx_drop': 0 - } + "rx_bytes": 0, + "rx_packets": 0, + "rx_errs": 0, + "rx_drop": 0, + "tx_bytes": 0, + "tx_packets": 0, + "tx_errs": 0, + "tx_drop": 0, + } for attrs in six.itervalues(nics): - if 'target' in attrs: - dev = attrs['target'] + if "target" in attrs: + dev = attrs["target"] stats = dom.interfaceStats(dev) - ret['rx_bytes'] += stats[0] - ret['rx_packets'] += stats[1] - ret['rx_errs'] += stats[2] - ret['rx_drop'] += stats[3] - ret['tx_bytes'] += stats[4] - ret['tx_packets'] += stats[5] - ret['tx_errs'] += stats[6] - ret['tx_drop'] += stats[7] + ret["rx_bytes"] += stats[0] + ret["rx_packets"] += stats[1] + ret["rx_errs"] += stats[2] + ret["rx_drop"] += stats[3] + ret["tx_bytes"] += stats[4] + ret["tx_packets"] += stats[5] + ret["tx_errs"] += stats[6] + ret["tx_drop"] += stats[7] return ret + info = {} conn = __get_conn(**kwargs) if vm_: @@ -3634,7 +4207,7 @@ def vm_netstats(vm_=None, **kwargs): def vm_diskstats(vm_=None, **kwargs): - ''' + """ Return disk usage counters used by the vms on this hyper in a list of dicts: @@ -3670,36 +4243,33 @@ def vm_diskstats(vm_=None, **kwargs): .. code-block:: bash salt '*' virt.vm_blockstats - ''' + """ + def get_disk_devs(dom): - ''' + """ Extract the disk devices names from the domain XML definition - ''' + """ doc = ElementTree.fromstring(get_xml(dom, **kwargs)) - return [target.get('dev') for target in doc.findall('devices/disk/target')] + return [target.get("dev") for target in doc.findall("devices/disk/target")] def _info(dom): - ''' + """ Compute the disk stats of a domain - ''' + """ # Do not use get_disks, since it uses qemu-img and is very slow # and unsuitable for any sort of real time statistics disks = get_disk_devs(dom) - ret = {'rd_req': 0, - 'rd_bytes': 0, - 'wr_req': 0, - 'wr_bytes': 0, - 'errs': 0 - } + ret = {"rd_req": 0, "rd_bytes": 0, "wr_req": 0, "wr_bytes": 0, "errs": 0} for disk in disks: stats = dom.blockStats(disk) - ret['rd_req'] += stats[0] - ret['rd_bytes'] += stats[1] - ret['wr_req'] += stats[2] - ret['wr_bytes'] += stats[3] - ret['errs'] += stats[4] + ret["rd_req"] += stats[0] + ret["rd_bytes"] += stats[1] + ret["wr_req"] += stats[2] + ret["wr_bytes"] += stats[3] + ret["errs"] += stats[4] return ret + info = {} conn = __get_conn(**kwargs) if vm_: @@ -3713,30 +4283,33 @@ def vm_diskstats(vm_=None, **kwargs): def _parse_snapshot_description(vm_snapshot, unix_time=False): - ''' + """ Parse XML doc and return a dict with the status values. :param xmldoc: :return: - ''' + """ ret = dict() tree = ElementTree.fromstring(vm_snapshot.getXMLDesc()) for node in tree: - if node.tag == 'name': - ret['name'] = node.text - elif node.tag == 'creationTime': - ret['created'] = datetime.datetime.fromtimestamp(float(node.text)).isoformat(' ') \ - if not unix_time else float(node.text) - elif node.tag == 'state': - ret['running'] = node.text == 'running' + if node.tag == "name": + ret["name"] = node.text + elif node.tag == "creationTime": + ret["created"] = ( + datetime.datetime.fromtimestamp(float(node.text)).isoformat(" ") + if not unix_time + else float(node.text) + ) + elif node.tag == "state": + ret["running"] = node.text == "running" - ret['current'] = vm_snapshot.isCurrent() == 1 + ret["current"] = vm_snapshot.isCurrent() == 1 return ret def list_snapshots(domain=None, **kwargs): - ''' + """ List available snapshots for certain vm or for all. :param domain: domain name @@ -3758,18 +4331,20 @@ def list_snapshots(domain=None, **kwargs): salt '*' virt.list_snapshots salt '*' virt.list_snapshots <domain> - ''' + """ ret = dict() conn = __get_conn(**kwargs) for vm_domain in _get_domain(conn, *(domain and [domain] or list()), iterable=True): - ret[vm_domain.name()] = [_parse_snapshot_description(snap) for snap in vm_domain.listAllSnapshots()] or 'N/A' + ret[vm_domain.name()] = [ + _parse_snapshot_description(snap) for snap in vm_domain.listAllSnapshots() + ] or "N/A" conn.close() return ret def snapshot(domain, name=None, suffix=None, **kwargs): - ''' + """ Create a snapshot of a VM. :param domain: domain name @@ -3795,18 +4370,22 @@ def snapshot(domain, name=None, suffix=None, **kwargs): .. code-block:: bash salt '*' virt.snapshot <domain> - ''' + """ if name and name.lower() == domain.lower(): - raise CommandExecutionError('Virtual Machine {name} is already defined. ' - 'Please choose another name for the snapshot'.format(name=name)) + raise CommandExecutionError( + "Virtual Machine {name} is already defined. " + "Please choose another name for the snapshot".format(name=name) + ) if not name: - name = "{domain}-{tsnap}".format(domain=domain, tsnap=time.strftime('%Y%m%d-%H%M%S', time.localtime())) + name = "{domain}-{tsnap}".format( + domain=domain, tsnap=time.strftime("%Y%m%d-%H%M%S", time.localtime()) + ) if suffix: name = "{name}-{suffix}".format(name=name, suffix=suffix) - doc = ElementTree.Element('domainsnapshot') - n_name = ElementTree.SubElement(doc, 'name') + doc = ElementTree.Element("domainsnapshot") + n_name = ElementTree.SubElement(doc, "name") n_name.text = name conn = __get_conn(**kwargs) @@ -3815,11 +4394,11 @@ def snapshot(domain, name=None, suffix=None, **kwargs): ) conn.close() - return {'name': name} + return {"name": name} def delete_snapshots(name, *names, **kwargs): - ''' + """ Delete one or more snapshots of the given VM. :param name: domain name @@ -3843,7 +4422,7 @@ def delete_snapshots(name, *names, **kwargs): salt '*' virt.delete_snapshots <domain> all=True salt '*' virt.delete_snapshots <domain> <snapshot> salt '*' virt.delete_snapshots <domain> <snapshot1> <snapshot2> ... - ''' + """ deleted = dict() conn = __get_conn(**kwargs) domain = _get_domain(conn, name) @@ -3853,13 +4432,16 @@ def delete_snapshots(name, *names, **kwargs): snap.delete() conn.close() - available = {name: [_parse_snapshot_description(snap) for snap in domain.listAllSnapshots()] or 'N/A'} + available = { + name: [_parse_snapshot_description(snap) for snap in domain.listAllSnapshots()] + or "N/A" + } - return {'available': available, 'deleted': deleted} + return {"available": available, "deleted": deleted} def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): - ''' + """ Revert snapshot to the previous from current (if available) or to the specific. :param name: domain name @@ -3883,7 +4465,7 @@ def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): salt '*' virt.revert <domain> salt '*' virt.revert <domain> <snapshot> - ''' + """ ret = dict() conn = __get_conn(**kwargs) domain = _get_domain(conn, name) @@ -3891,22 +4473,32 @@ def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): _snapshots = list() for snap_obj in snapshots: - _snapshots.append({'idx': _parse_snapshot_description(snap_obj, unix_time=True)['created'], 'ptr': snap_obj}) - snapshots = [w_ptr['ptr'] for w_ptr in sorted(_snapshots, key=lambda item: item['idx'], reverse=True)] + _snapshots.append( + { + "idx": _parse_snapshot_description(snap_obj, unix_time=True)["created"], + "ptr": snap_obj, + } + ) + snapshots = [ + w_ptr["ptr"] + for w_ptr in sorted(_snapshots, key=lambda item: item["idx"], reverse=True) + ] del _snapshots if not snapshots: conn.close() - raise CommandExecutionError('No snapshots found') + raise CommandExecutionError("No snapshots found") elif len(snapshots) == 1: conn.close() - raise CommandExecutionError('Cannot revert to itself: only one snapshot is available.') + raise CommandExecutionError( + "Cannot revert to itself: only one snapshot is available." + ) snap = None for p_snap in snapshots: if not vm_snapshot: - if p_snap.isCurrent() and snapshots[snapshots.index(p_snap) + 1:]: - snap = snapshots[snapshots.index(p_snap) + 1:][0] + if p_snap.isCurrent() and snapshots[snapshots.index(p_snap) + 1 :]: + snap = snapshots[snapshots.index(p_snap) + 1 :][0] break elif p_snap.getName() == vm_snapshot: snap = p_snap @@ -3915,13 +4507,16 @@ def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): if not snap: conn.close() raise CommandExecutionError( - snapshot and 'Snapshot "{0}" not found'.format(vm_snapshot) or 'No more previous snapshots available') + snapshot + and 'Snapshot "{0}" not found'.format(vm_snapshot) + or "No more previous snapshots available" + ) elif snap.isCurrent(): conn.close() - raise CommandExecutionError('Cannot revert to the currently running snapshot.') + raise CommandExecutionError("Cannot revert to the currently running snapshot.") domain.revertToSnapshot(snap) - ret['reverted'] = snap.getName() + ret["reverted"] = snap.getName() if cleanup: delete = list() @@ -3931,9 +4526,9 @@ def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): p_snap.delete() else: break - ret['deleted'] = delete + ret["deleted"] = delete else: - ret['deleted'] = 'N/A' + ret["deleted"] = "N/A" conn.close() @@ -3941,12 +4536,12 @@ def revert_snapshot(name, vm_snapshot=None, cleanup=False, **kwargs): def _caps_add_machine(machines, node): - ''' + """ Parse the <machine> element of the host capabilities and add it to the machines list. - ''' - maxcpus = node.get('maxCpus') - canonical = node.get('canonical') + """ + maxcpus = node.get("maxCpus") + canonical = node.get("canonical") name = node.text alternate_name = "" @@ -3956,202 +4551,237 @@ def _caps_add_machine(machines, node): machine = machines.get(name) if not machine: - machine = {'alternate_names': []} + machine = {"alternate_names": []} if maxcpus: - machine['maxcpus'] = int(maxcpus) + machine["maxcpus"] = int(maxcpus) machines[name] = machine if alternate_name: - machine['alternate_names'].append(alternate_name) + machine["alternate_names"].append(alternate_name) def _parse_caps_guest(guest): - ''' + """ Parse the <guest> element of the connection capabilities XML - ''' - arch_node = guest.find('arch') + """ + arch_node = guest.find("arch") result = { - 'os_type': guest.find('os_type').text, - 'arch': { - 'name': arch_node.get('name'), - 'machines': {}, - 'domains': {} - }, + "os_type": guest.find("os_type").text, + "arch": {"name": arch_node.get("name"), "machines": {}, "domains": {}}, } for child in arch_node: - if child.tag == 'wordsize': - result['arch']['wordsize'] = int(child.text) - elif child.tag == 'emulator': - result['arch']['emulator'] = child.text - elif child.tag == 'machine': - _caps_add_machine(result['arch']['machines'], child) - elif child.tag == 'domain': - domain_type = child.get('type') - domain = { - 'emulator': None, - 'machines': {} - } - emulator_node = child.find('emulator') + if child.tag == "wordsize": + result["arch"]["wordsize"] = int(child.text) + elif child.tag == "emulator": + result["arch"]["emulator"] = child.text + elif child.tag == "machine": + _caps_add_machine(result["arch"]["machines"], child) + elif child.tag == "domain": + domain_type = child.get("type") + domain = {"emulator": None, "machines": {}} + emulator_node = child.find("emulator") if emulator_node is not None: - domain['emulator'] = emulator_node.text - for machine in child.findall('machine'): - _caps_add_machine(domain['machines'], machine) - result['arch']['domains'][domain_type] = domain + domain["emulator"] = emulator_node.text + for machine in child.findall("machine"): + _caps_add_machine(domain["machines"], machine) + result["arch"]["domains"][domain_type] = domain # Note that some features have no default and toggle attributes. # This may not be a perfect match, but represent them as enabled by default # without possibility to toggle them. # Some guests may also have no feature at all (xen pv for instance) - features_nodes = guest.find('features') + features_nodes = guest.find("features") if features_nodes is not None: - result['features'] = {child.tag: {'toggle': True if child.get('toggle') == 'yes' else False, - 'default': True if child.get('default') == 'no' else True} - for child in features_nodes} + result["features"] = { + child.tag: { + "toggle": True if child.get("toggle") == "yes" else False, + "default": True if child.get("default") == "no" else True, + } + for child in features_nodes + } return result def _parse_caps_cell(cell): - ''' + """ Parse the <cell> nodes of the connection capabilities XML output. - ''' - result = { - 'id': int(cell.get('id')) - } + """ + result = {"id": int(cell.get("id"))} - mem_node = cell.find('memory') + mem_node = cell.find("memory") if mem_node is not None: - unit = mem_node.get('unit', 'KiB') + unit = mem_node.get("unit", "KiB") memory = mem_node.text - result['memory'] = "{} {}".format(memory, unit) + result["memory"] = "{} {}".format(memory, unit) - pages = [{'size': "{} {}".format(page.get('size'), page.get('unit', 'KiB')), - 'available': int(page.text)} - for page in cell.findall('pages')] + pages = [ + { + "size": "{} {}".format(page.get("size"), page.get("unit", "KiB")), + "available": int(page.text), + } + for page in cell.findall("pages") + ] if pages: - result['pages'] = pages + result["pages"] = pages - distances = {int(distance.get('id')): int(distance.get('value')) - for distance in cell.findall('distances/sibling')} + distances = { + int(distance.get("id")): int(distance.get("value")) + for distance in cell.findall("distances/sibling") + } if distances: - result['distances'] = distances + result["distances"] = distances cpus = [] - for cpu_node in cell.findall('cpus/cpu'): - cpu = { - 'id': int(cpu_node.get('id')) - } - socket_id = cpu_node.get('socket_id') + for cpu_node in cell.findall("cpus/cpu"): + cpu = {"id": int(cpu_node.get("id"))} + socket_id = cpu_node.get("socket_id") if socket_id: - cpu['socket_id'] = int(socket_id) + cpu["socket_id"] = int(socket_id) - core_id = cpu_node.get('core_id') + core_id = cpu_node.get("core_id") if core_id: - cpu['core_id'] = int(core_id) - siblings = cpu_node.get('siblings') + cpu["core_id"] = int(core_id) + siblings = cpu_node.get("siblings") if siblings: - cpu['siblings'] = siblings + cpu["siblings"] = siblings cpus.append(cpu) if cpus: - result['cpus'] = cpus + result["cpus"] = cpus return result def _parse_caps_bank(bank): - ''' + """ Parse the <bank> element of the connection capabilities XML. - ''' + """ result = { - 'id': int(bank.get('id')), - 'level': int(bank.get('level')), - 'type': bank.get('type'), - 'size': "{} {}".format(bank.get('size'), bank.get('unit')), - 'cpus': bank.get('cpus') + "id": int(bank.get("id")), + "level": int(bank.get("level")), + "type": bank.get("type"), + "size": "{} {}".format(bank.get("size"), bank.get("unit")), + "cpus": bank.get("cpus"), } controls = [] - for control in bank.findall('control'): - unit = control.get('unit') + for control in bank.findall("control"): + unit = control.get("unit") result_control = { - 'granularity': "{} {}".format(control.get('granularity'), unit), - 'type': control.get('type'), - 'maxAllocs': int(control.get('maxAllocs')) + "granularity": "{} {}".format(control.get("granularity"), unit), + "type": control.get("type"), + "maxAllocs": int(control.get("maxAllocs")), } - minimum = control.get('min') + minimum = control.get("min") if minimum: - result_control['min'] = "{} {}".format(minimum, unit) + result_control["min"] = "{} {}".format(minimum, unit) controls.append(result_control) if controls: - result['controls'] = controls + result["controls"] = controls return result def _parse_caps_host(host): - ''' + """ Parse the <host> element of the connection capabilities XML. - ''' + """ result = {} for child in host: - if child.tag == 'uuid': - result['uuid'] = child.text + if child.tag == "uuid": + result["uuid"] = child.text - elif child.tag == 'cpu': + elif child.tag == "cpu": cpu = { - 'arch': child.find('arch').text if child.find('arch') is not None else None, - 'model': child.find('model').text if child.find('model') is not None else None, - 'vendor': child.find('vendor').text if child.find('vendor') is not None else None, - 'features': [feature.get('name') for feature in child.findall('feature')], - 'pages': [{'size': '{} {}'.format(page.get('size'), page.get('unit', 'KiB'))} - for page in child.findall('pages')] + "arch": child.find("arch").text + if child.find("arch") is not None + else None, + "model": child.find("model").text + if child.find("model") is not None + else None, + "vendor": child.find("vendor").text + if child.find("vendor") is not None + else None, + "features": [ + feature.get("name") for feature in child.findall("feature") + ], + "pages": [ + {"size": "{} {}".format(page.get("size"), page.get("unit", "KiB"))} + for page in child.findall("pages") + ], } # Parse the cpu tag - microcode = child.find('microcode') + microcode = child.find("microcode") if microcode is not None: - cpu['microcode'] = microcode.get('version') + cpu["microcode"] = microcode.get("version") - topology = child.find('topology') + topology = child.find("topology") if topology is not None: - cpu['sockets'] = int(topology.get('sockets')) - cpu['cores'] = int(topology.get('cores')) - cpu['threads'] = int(topology.get('threads')) - result['cpu'] = cpu + cpu["sockets"] = int(topology.get("sockets")) + cpu["cores"] = int(topology.get("cores")) + cpu["threads"] = int(topology.get("threads")) + result["cpu"] = cpu elif child.tag == "power_management": - result['power_management'] = [node.tag for node in child] + result["power_management"] = [node.tag for node in child] elif child.tag == "migration_features": - result['migration'] = { - 'live': child.find('live') is not None, - 'transports': [node.text for node in child.findall('uri_transports/uri_transport')] + result["migration"] = { + "live": child.find("live") is not None, + "transports": [ + node.text for node in child.findall("uri_transports/uri_transport") + ], } elif child.tag == "topology": - result['topology'] = { - 'cells': [_parse_caps_cell(cell) for cell in child.findall('cells/cell')] + result["topology"] = { + "cells": [ + _parse_caps_cell(cell) for cell in child.findall("cells/cell") + ] } - elif child.tag == 'cache': - result['cache'] = { - 'banks': [_parse_caps_bank(bank) for bank in child.findall('bank')] + elif child.tag == "cache": + result["cache"] = { + "banks": [_parse_caps_bank(bank) for bank in child.findall("bank")] } - result['security'] = [{ - 'model': secmodel.find('model').text if secmodel.find('model') is not None else None, - 'doi': secmodel.find('doi').text if secmodel.find('doi') is not None else None, - 'baselabels': [{'type': label.get('type'), 'label': label.text} - for label in secmodel.findall('baselabel')] + result["security"] = [ + { + "model": secmodel.find("model").text + if secmodel.find("model") is not None + else None, + "doi": secmodel.find("doi").text + if secmodel.find("doi") is not None + else None, + "baselabels": [ + {"type": label.get("type"), "label": label.text} + for label in secmodel.findall("baselabel") + ], } - for secmodel in host.findall('secmodel')] + for secmodel in host.findall("secmodel") + ] return result +def _capabilities(conn): + """ + Return the hypervisor connection capabilities. + + :param conn: opened libvirt connection to use + """ + caps = ElementTree.fromstring(conn.getCapabilities()) + + return { + "host": _parse_caps_host(caps.find("host")), + "guests": [_parse_caps_guest(guest) for guest in caps.findall("guest")], + } + + def capabilities(**kwargs): - ''' + """ Return the hypervisor connection capabilities. :param connection: libvirt connection URI, overriding defaults @@ -4165,149 +4795,156 @@ def capabilities(**kwargs): .. code-block:: bash salt '*' virt.capabilities - ''' + """ conn = __get_conn(**kwargs) - caps = ElementTree.fromstring(conn.getCapabilities()) - conn.close() - - return { - 'host': _parse_caps_host(caps.find('host')), - 'guests': [_parse_caps_guest(guest) for guest in caps.findall('guest')] - } + try: + caps = _capabilities(conn) + except libvirt.libvirtError as err: + raise CommandExecutionError(str(err)) + finally: + conn.close() + return caps def _parse_caps_enum(node): - ''' + """ Return a tuple containing the name of the enum and the possible values - ''' - return (node.get('name'), [value.text for value in node.findall('value')]) + """ + return (node.get("name"), [value.text for value in node.findall("value")]) def _parse_caps_cpu(node): - ''' + """ Parse the <cpu> element of the domain capabilities - ''' + """ result = {} - for mode in node.findall('mode'): - if not mode.get('supported') == 'yes': + for mode in node.findall("mode"): + if not mode.get("supported") == "yes": continue - name = mode.get('name') - if name == 'host-passthrough': + name = mode.get("name") + if name == "host-passthrough": result[name] = True - elif name == 'host-model': + elif name == "host-model": host_model = {} - model_node = mode.find('model') + model_node = mode.find("model") if model_node is not None: - model = { - 'name': model_node.text - } + model = {"name": model_node.text} - vendor_id = model_node.get('vendor_id') + vendor_id = model_node.get("vendor_id") if vendor_id: - model['vendor_id'] = vendor_id + model["vendor_id"] = vendor_id - fallback = model_node.get('fallback') + fallback = model_node.get("fallback") if fallback: - model['fallback'] = fallback - host_model['model'] = model + model["fallback"] = fallback + host_model["model"] = model - vendor = mode.find('vendor').text if mode.find('vendor') is not None else None + vendor = ( + mode.find("vendor").text if mode.find("vendor") is not None else None + ) if vendor: - host_model['vendor'] = vendor + host_model["vendor"] = vendor - features = {feature.get('name'): feature.get('policy') for feature in mode.findall('feature')} + features = { + feature.get("name"): feature.get("policy") + for feature in mode.findall("feature") + } if features: - host_model['features'] = features + host_model["features"] = features result[name] = host_model - elif name == 'custom': + elif name == "custom": custom_model = {} - models = {model.text: model.get('usable') for model in mode.findall('model')} + models = { + model.text: model.get("usable") for model in mode.findall("model") + } if models: - custom_model['models'] = models + custom_model["models"] = models result[name] = custom_model return result def _parse_caps_devices_features(node): - ''' + """ Parse the devices or features list of the domain capatilities - ''' + """ result = {} for child in node: - if child.get('supported') == 'yes': - enums = [_parse_caps_enum(node) for node in child.findall('enum')] + if child.get("supported") == "yes": + enums = [_parse_caps_enum(node) for node in child.findall("enum")] result[child.tag] = {item[0]: item[1] for item in enums if item[0]} return result def _parse_caps_loader(node): - ''' + """ Parse the <loader> element of the domain capabilities. - ''' - enums = [_parse_caps_enum(enum) for enum in node.findall('enum')] + """ + enums = [_parse_caps_enum(enum) for enum in node.findall("enum")] result = {item[0]: item[1] for item in enums if item[0]} - values = [child.text for child in node.findall('value')] + values = [child.text for child in node.findall("value")] if values: - result['values'] = values + result["values"] = values return result def _parse_domain_caps(caps): - ''' + """ Parse the XML document of domain capabilities into a structure. - ''' + """ result = { - 'emulator': caps.find('path').text if caps.find('path') is not None else None, - 'domain': caps.find('domain').text if caps.find('domain') is not None else None, - 'machine': caps.find('machine').text if caps.find('machine') is not None else None, - 'arch': caps.find('arch').text if caps.find('arch') is not None else None + "emulator": caps.find("path").text if caps.find("path") is not None else None, + "domain": caps.find("domain").text if caps.find("domain") is not None else None, + "machine": caps.find("machine").text + if caps.find("machine") is not None + else None, + "arch": caps.find("arch").text if caps.find("arch") is not None else None, } for child in caps: - if child.tag == 'vcpu' and child.get('max'): - result['max_vcpus'] = int(child.get('max')) + if child.tag == "vcpu" and child.get("max"): + result["max_vcpus"] = int(child.get("max")) - elif child.tag == 'iothreads': - result['iothreads'] = child.get('supported') == 'yes' + elif child.tag == "iothreads": + result["iothreads"] = child.get("supported") == "yes" - elif child.tag == 'os': - result['os'] = {} - loader_node = child.find('loader') - if loader_node is not None and loader_node.get('supported') == 'yes': + elif child.tag == "os": + result["os"] = {} + loader_node = child.find("loader") + if loader_node is not None and loader_node.get("supported") == "yes": loader = _parse_caps_loader(loader_node) - result['os']['loader'] = loader + result["os"]["loader"] = loader - elif child.tag == 'cpu': + elif child.tag == "cpu": cpu = _parse_caps_cpu(child) if cpu: - result['cpu'] = cpu + result["cpu"] = cpu - elif child.tag == 'devices': + elif child.tag == "devices": devices = _parse_caps_devices_features(child) if devices: - result['devices'] = devices + result["devices"] = devices - elif child.tag == 'features': + elif child.tag == "features": features = _parse_caps_devices_features(child) if features: - result['features'] = features + result["features"] = features return result def domain_capabilities(emulator=None, arch=None, machine=None, domain=None, **kwargs): - ''' + """ Return the domain capabilities given an emulator, architecture, machine or virtualization type. - .. versionadded:: Fluorine + .. versionadded:: 2019.2.0 :param emulator: return the capabilities for the given emulator binary :param arch: return the capabilities for the given CPU architecture @@ -4328,11 +4965,13 @@ def domain_capabilities(emulator=None, arch=None, machine=None, domain=None, **k salt '*' virt.domain_capabilities arch='x86_64' domain='kvm' - ''' + """ conn = __get_conn(**kwargs) result = [] try: - caps = ElementTree.fromstring(conn.getDomainCapabilities(emulator, arch, machine, domain, 0)) + caps = ElementTree.fromstring( + conn.getDomainCapabilities(emulator, arch, machine, domain, 0) + ) result = _parse_domain_caps(caps) finally: conn.close() @@ -4341,10 +4980,10 @@ def domain_capabilities(emulator=None, arch=None, machine=None, domain=None, **k def all_capabilities(**kwargs): - ''' + """ Return the host and domain capabilities in a single call. - .. versionadded:: Neon + .. versionadded:: Sodium :param connection: libvirt connection URI, overriding defaults :param username: username to connect with, overriding defaults @@ -4356,31 +4995,45 @@ def all_capabilities(**kwargs): salt '*' virt.all_capabilities - ''' + """ conn = __get_conn(**kwargs) result = {} try: host_caps = ElementTree.fromstring(conn.getCapabilities()) - domains = [[(guest.get('arch', {}).get('name', None), key) - for key in guest.get('arch', {}).get('domains', {}).keys()] - for guest in [_parse_caps_guest(guest) for guest in host_caps.findall('guest')]] + domains = [ + [ + (guest.get("arch", {}).get("name", None), key) + for key in guest.get("arch", {}).get("domains", {}).keys() + ] + for guest in [ + _parse_caps_guest(guest) for guest in host_caps.findall("guest") + ] + ] flattened = [pair for item in (x for x in domains) for pair in item] result = { - 'host': { - 'host': _parse_caps_host(host_caps.find('host')), - 'guests': [_parse_caps_guest(guest) for guest in host_caps.findall('guest')] - }, - 'domains': [_parse_domain_caps(ElementTree.fromstring( - conn.getDomainCapabilities(None, arch, None, domain))) - for (arch, domain) in flattened]} + "host": { + "host": _parse_caps_host(host_caps.find("host")), + "guests": [ + _parse_caps_guest(guest) for guest in host_caps.findall("guest") + ], + }, + "domains": [ + _parse_domain_caps( + ElementTree.fromstring( + conn.getDomainCapabilities(None, arch, None, domain) + ) + ) + for (arch, domain) in flattened + ], + } finally: conn.close() return result -def cpu_baseline(full=False, migratable=False, out='libvirt', **kwargs): - ''' +def cpu_baseline(full=False, migratable=False, out="libvirt", **kwargs): + """ Return the optimal 'custom' CPU baseline config for VM's on this minion .. versionadded:: 2016.3.0 @@ -4404,72 +5057,80 @@ def cpu_baseline(full=False, migratable=False, out='libvirt', **kwargs): salt '*' virt.cpu_baseline - ''' + """ conn = __get_conn(**kwargs) caps = ElementTree.fromstring(conn.getCapabilities()) - cpu = caps.find('host/cpu') - log.debug('Host CPU model definition: %s', salt.utils.stringutils.to_str(ElementTree.tostring(cpu))) + cpu = caps.find("host/cpu") + log.debug( + "Host CPU model definition: %s", + salt.utils.stringutils.to_str(ElementTree.tostring(cpu)), + ) flags = 0 if migratable: # This one is only in 1.2.14+ - if getattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_MIGRATABLE', False): + if getattr(libvirt, "VIR_CONNECT_BASELINE_CPU_MIGRATABLE", False): flags += libvirt.VIR_CONNECT_BASELINE_CPU_MIGRATABLE else: conn.close() raise ValueError - if full and getattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES', False): + if full and getattr(libvirt, "VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES", False): # This one is only in 1.1.3+ flags += libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES - cpu = ElementTree.fromstring(conn.baselineCPU([salt.utils.stringutils.to_str(ElementTree.tostring(cpu))], flags)) + cpu = ElementTree.fromstring( + conn.baselineCPU( + [salt.utils.stringutils.to_str(ElementTree.tostring(cpu))], flags + ) + ) conn.close() - if full and not getattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES', False): + if full and not getattr(libvirt, "VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES", False): # Try do it by ourselves # Find the models in cpu_map.xml and iterate over them for as long as entries have submodels - with salt.utils.files.fopen('/usr/share/libvirt/cpu_map.xml', 'r') as cpu_map: + with salt.utils.files.fopen("/usr/share/libvirt/cpu_map.xml", "r") as cpu_map: cpu_map = ElementTree.parse(cpu_map) - cpu_model = cpu.find('model').text + cpu_model = cpu.find("model").text while cpu_model: - cpu_map_models = cpu_map.findall('arch/model') - cpu_specs = [el for el in cpu_map_models if el.get('name') == cpu_model and bool(len(el))] + cpu_map_models = cpu_map.findall("arch/model") + cpu_specs = [ + el + for el in cpu_map_models + if el.get("name") == cpu_model and bool(len(el)) + ] if not cpu_specs: - raise ValueError('Model {0} not found in CPU map'.format(cpu_model)) + raise ValueError("Model {0} not found in CPU map".format(cpu_model)) elif len(cpu_specs) > 1: - raise ValueError('Multiple models {0} found in CPU map'.format(cpu_model)) + raise ValueError( + "Multiple models {0} found in CPU map".format(cpu_model) + ) cpu_specs = cpu_specs[0] # libvirt's cpu map used to nest model elements, to point the parent model. # keep this code for compatibility with old libvirt versions - model_node = cpu_specs.find('model') + model_node = cpu_specs.find("model") if model_node is None: cpu_model = None else: - cpu_model = model_node.get('name') + cpu_model = model_node.get("name") - cpu.extend([feature for feature in cpu_specs.findall('feature')]) + cpu.extend([feature for feature in cpu_specs.findall("feature")]) - if out == 'salt': + if out == "salt": return { - 'model': cpu.find('model').text, - 'vendor': cpu.find('vendor').text, - 'features': [feature.get('name') for feature in cpu.findall('feature')] + "model": cpu.find("model").text, + "vendor": cpu.find("vendor").text, + "features": [feature.get("name") for feature in cpu.findall("feature")], } return cpu.toxml() -def network_define(name, - bridge, - forward, - ipv4_config=None, - ipv6_config=None, - **kwargs): - ''' +def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **kwargs): + """ Create libvirt network. :param name: Network name @@ -4518,12 +5179,12 @@ def network_define(name, salt '*' virt.network_define network main bridge openvswitch .. versionadded:: 2019.2.0 - ''' + """ conn = __get_conn(**kwargs) - vport = kwargs.get('vport', None) - tag = kwargs.get('tag', None) - autostart = kwargs.get('autostart', True) - starting = kwargs.get('start', True) + vport = kwargs.get("vport", None) + tag = kwargs.get("tag", None) + autostart = kwargs.get("autostart", True) + starting = kwargs.get("start", True) net_xml = _gen_net_xml( name, @@ -4535,14 +5196,14 @@ def network_define(name, ) try: conn.networkDefineXML(net_xml) - except libvirtError as err: + except libvirt.libvirtError as err: log.warning(err) conn.close() raise err # a real error we should report upwards try: network = conn.networkLookupByName(name) - except libvirtError as err: + except libvirt.libvirtError as err: log.warning(err) conn.close() raise err # a real error we should report upwards @@ -4565,7 +5226,7 @@ def network_define(name, def list_networks(**kwargs): - ''' + """ List all virtual networks. :param connection: libvirt connection URI, overriding defaults @@ -4579,7 +5240,7 @@ def list_networks(**kwargs): .. code-block:: bash salt '*' virt.list_networks - ''' + """ conn = __get_conn(**kwargs) try: return [net.name() for net in conn.listAllNetworks()] @@ -4588,7 +5249,7 @@ def list_networks(**kwargs): def network_info(name=None, **kwargs): - ''' + """ Return informations on a virtual network provided its name. :param name: virtual network name @@ -4605,42 +5266,48 @@ def network_info(name=None, **kwargs): .. code-block:: bash salt '*' virt.network_info default - ''' + """ result = {} conn = __get_conn(**kwargs) def _net_get_leases(net): - ''' + """ Get all DHCP leases for a network - ''' + """ leases = net.DHCPLeases() for lease in leases: - if lease['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV4: - lease['type'] = 'ipv4' - elif lease['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV6: - lease['type'] = 'ipv6' + if lease["type"] == libvirt.VIR_IP_ADDR_TYPE_IPV4: + lease["type"] = "ipv4" + elif lease["type"] == libvirt.VIR_IP_ADDR_TYPE_IPV6: + lease["type"] = "ipv6" else: - lease['type'] = 'unknown' + lease["type"] = "unknown" return leases try: - nets = [net for net in conn.listAllNetworks() if name is None or net.name() == name] - result = {net.name(): { - 'uuid': net.UUIDString(), - 'bridge': net.bridgeName(), - 'autostart': net.autostart(), - 'active': net.isActive(), - 'persistent': net.isPersistent(), - 'leases': _net_get_leases(net)} for net in nets} + nets = [ + net for net in conn.listAllNetworks() if name is None or net.name() == name + ] + result = { + net.name(): { + "uuid": net.UUIDString(), + "bridge": net.bridgeName(), + "autostart": net.autostart(), + "active": net.isActive(), + "persistent": net.isPersistent(), + "leases": _net_get_leases(net), + } + for net in nets + } except libvirt.libvirtError as err: - log.debug('Silenced libvirt error: %s', str(err)) + log.debug("Silenced libvirt error: %s", str(err)) finally: conn.close() return result def network_get_xml(name, **kwargs): - ''' + """ Return the XML definition of a virtual network :param name: libvirt network name @@ -4655,7 +5322,7 @@ def network_get_xml(name, **kwargs): .. code-block:: bash salt '*' virt.network_get_xml default - ''' + """ conn = __get_conn(**kwargs) try: return conn.networkLookupByName(name).XMLDesc() @@ -4664,7 +5331,7 @@ def network_get_xml(name, **kwargs): def network_start(name, **kwargs): - ''' + """ Start a defined virtual network. :param name: virtual network name @@ -4679,7 +5346,7 @@ def network_start(name, **kwargs): .. code-block:: bash salt '*' virt.network_start default - ''' + """ conn = __get_conn(**kwargs) try: net = conn.networkLookupByName(name) @@ -4689,7 +5356,7 @@ def network_start(name, **kwargs): def network_stop(name, **kwargs): - ''' + """ Stop a defined virtual network. :param name: virtual network name @@ -4704,7 +5371,7 @@ def network_stop(name, **kwargs): .. code-block:: bash salt '*' virt.network_stop default - ''' + """ conn = __get_conn(**kwargs) try: net = conn.networkLookupByName(name) @@ -4714,7 +5381,7 @@ def network_stop(name, **kwargs): def network_undefine(name, **kwargs): - ''' + """ Remove a defined virtual network. This does not stop the virtual network. :param name: virtual network name @@ -4729,7 +5396,7 @@ def network_undefine(name, **kwargs): .. code-block:: bash salt '*' virt.network_undefine default - ''' + """ conn = __get_conn(**kwargs) try: net = conn.networkLookupByName(name) @@ -4738,8 +5405,8 @@ def network_undefine(name, **kwargs): conn.close() -def network_set_autostart(name, state='on', **kwargs): - ''' +def network_set_autostart(name, state="on", **kwargs): + """ Set the autostart flag on a virtual network so that the network will start with the host system on reboot. @@ -4757,45 +5424,216 @@ def network_set_autostart(name, state='on', **kwargs): .. code-block:: bash salt "*" virt.network_set_autostart <pool> <on | off> - ''' + """ conn = __get_conn(**kwargs) try: net = conn.networkLookupByName(name) - return not bool(net.setAutostart(1 if state == 'on' else 0)) + return not bool(net.setAutostart(1 if state == "on" else 0)) finally: conn.close() def _parse_pools_caps(doc): - ''' + """ Parse libvirt pool capabilities XML - ''' + """ + def _parse_pool_caps(pool): pool_caps = { - 'name': pool.get('type'), - 'supported': pool.get('supported', 'no') == 'yes' + "name": pool.get("type"), + "supported": pool.get("supported", "no") == "yes", } - for option_kind in ['pool', 'vol']: + for option_kind in ["pool", "vol"]: options = {} - default_format_node = pool.find('{0}Options/defaultFormat'.format(option_kind)) + default_format_node = pool.find( + "{0}Options/defaultFormat".format(option_kind) + ) if default_format_node is not None: - options['default_format'] = default_format_node.get('type') - options_enums = {enum.get('name'): [value.text for value in enum.findall('value')] - for enum in pool.findall('{0}Options/enum'.format(option_kind))} + options["default_format"] = default_format_node.get("type") + options_enums = { + enum.get("name"): [value.text for value in enum.findall("value")] + for enum in pool.findall("{0}Options/enum".format(option_kind)) + } if options_enums: options.update(options_enums) if options: - if 'options' not in pool_caps: - pool_caps['options'] = {} - kind = option_kind if option_kind is not 'vol' else 'volume' - pool_caps['options'][kind] = options + if "options" not in pool_caps: + pool_caps["options"] = {} + kind = option_kind if option_kind is not "vol" else "volume" + pool_caps["options"][kind] = options return pool_caps - return [_parse_pool_caps(pool) for pool in doc.findall('pool')] + return [_parse_pool_caps(pool) for pool in doc.findall("pool")] + + +def _pool_capabilities(conn): + """ + Return the hypervisor connection storage pool capabilities. + + :param conn: opened libvirt connection to use + """ + has_pool_capabilities = bool(getattr(conn, "getStoragePoolCapabilities", None)) + if has_pool_capabilities: + caps = ElementTree.fromstring(conn.getStoragePoolCapabilities()) + pool_types = _parse_pools_caps(caps) + else: + # Compute reasonable values + all_hypervisors = ["xen", "kvm", "bhyve"] + images_formats = [ + "none", + "raw", + "dir", + "bochs", + "cloop", + "dmg", + "iso", + "vpc", + "vdi", + "fat", + "vhd", + "ploop", + "cow", + "qcow", + "qcow2", + "qed", + "vmdk", + ] + common_drivers = [ + { + "name": "fs", + "default_source_format": "auto", + "source_formats": [ + "auto", + "ext2", + "ext3", + "ext4", + "ufs", + "iso9660", + "udf", + "gfs", + "gfs2", + "vfat", + "hfs+", + "xfs", + "ocfs2", + ], + "default_target_format": "raw", + "target_formats": images_formats, + }, + { + "name": "dir", + "default_target_format": "raw", + "target_formats": images_formats, + }, + {"name": "iscsi"}, + {"name": "scsi"}, + { + "name": "logical", + "default_source_format": "lvm2", + "source_formats": ["unknown", "lvm2"], + }, + { + "name": "netfs", + "default_source_format": "auto", + "source_formats": ["auto", "nfs", "glusterfs", "cifs"], + "default_target_format": "raw", + "target_formats": images_formats, + }, + { + "name": "disk", + "default_source_format": "unknown", + "source_formats": [ + "unknown", + "dos", + "dvh", + "gpt", + "mac", + "bsd", + "pc98", + "sun", + "lvm2", + ], + "default_target_format": "none", + "target_formats": [ + "none", + "linux", + "fat16", + "fat32", + "linux-swap", + "linux-lvm", + "linux-raid", + "extended", + ], + }, + {"name": "mpath"}, + {"name": "rbd", "default_target_format": "raw", "target_formats": []}, + { + "name": "sheepdog", + "version": 10000, + "hypervisors": ["kvm"], + "default_target_format": "raw", + "target_formats": images_formats, + }, + { + "name": "gluster", + "version": 1002000, + "hypervisors": ["kvm"], + "default_target_format": "raw", + "target_formats": images_formats, + }, + {"name": "zfs", "version": 1002008, "hypervisors": ["bhyve"]}, + { + "name": "iscsi-direct", + "version": 4007000, + "hypervisors": ["kvm", "xen"], + }, + ] + + libvirt_version = conn.getLibVersion() + hypervisor = get_hypervisor() + + def _get_backend_output(backend): + output = { + "name": backend["name"], + "supported": ( + not backend.get("version") or libvirt_version >= backend["version"] + ) + and hypervisor in backend.get("hypervisors", all_hypervisors), + "options": { + "pool": { + "default_format": backend.get("default_source_format"), + "sourceFormatType": backend.get("source_formats"), + }, + "volume": { + "default_format": backend.get("default_target_format"), + "targetFormatType": backend.get("target_formats"), + }, + }, + } + + # Cleanup the empty members to match the libvirt output + for option_kind in ["pool", "volume"]: + if not [ + value + for value in output["options"][option_kind].values() + if value is not None + ]: + del output["options"][option_kind] + if not output["options"]: + del output["options"] + + return output + + pool_types = [_get_backend_output(backend) for backend in common_drivers] + + return { + "computed": not has_pool_capabilities, + "pool_types": pool_types, + } def pool_capabilities(**kwargs): - ''' + """ Return the hypervisor connection storage pool capabilities. The returned data are either directly extracted from libvirt or computed. @@ -4814,132 +5652,32 @@ def pool_capabilities(**kwargs): salt '*' virt.pool_capabilities - ''' + """ try: conn = __get_conn(**kwargs) - has_pool_capabilities = bool(getattr(conn, 'getStoragePoolCapabilities', None)) - if has_pool_capabilities: - caps = ElementTree.fromstring(conn.getStoragePoolCapabilities()) - pool_types = _parse_pools_caps(caps) - else: - # Compute reasonable values - all_hypervisors = ['xen', 'kvm', 'bhyve'] - images_formats = ['none', 'raw', 'dir', 'bochs', 'cloop', 'dmg', 'iso', 'vpc', 'vdi', - 'fat', 'vhd', 'ploop', 'cow', 'qcow', 'qcow2', 'qed', 'vmdk'] - common_drivers = [ - { - 'name': 'fs', - 'default_source_format': 'auto', - 'source_formats': ['auto', 'ext2', 'ext3', 'ext4', 'ufs', 'iso9660', 'udf', 'gfs', 'gfs2', - 'vfat', 'hfs+', 'xfs', 'ocfs2'], - 'default_target_format': 'raw', - 'target_formats': images_formats - }, - { - 'name': 'dir', - 'default_target_format': 'raw', - 'target_formats': images_formats - }, - {'name': 'iscsi'}, - {'name': 'scsi'}, - { - 'name': 'logical', - 'default_source_format': 'lvm2', - 'source_formats': ['unknown', 'lvm2'], - }, - { - 'name': 'netfs', - 'default_source_format': 'auto', - 'source_formats': ['auto', 'nfs', 'glusterfs', 'cifs'], - 'default_target_format': 'raw', - 'target_formats': images_formats - }, - { - 'name': 'disk', - 'default_source_format': 'unknown', - 'source_formats': ['unknown', 'dos', 'dvh', 'gpt', 'mac', 'bsd', 'pc98', 'sun', 'lvm2'], - 'default_target_format': 'none', - 'target_formats': ['none', 'linux', 'fat16', 'fat32', 'linux-swap', 'linux-lvm', - 'linux-raid', 'extended'] - }, - {'name': 'mpath'}, - { - 'name': 'rbd', - 'default_target_format': 'raw', - 'target_formats': [] - }, - { - 'name': 'sheepdog', - 'version': 10000, - 'hypervisors': ['kvm'], - 'default_target_format': 'raw', - 'target_formats': images_formats - }, - { - 'name': 'gluster', - 'version': 1002000, - 'hypervisors': ['kvm'], - 'default_target_format': 'raw', - 'target_formats': images_formats - }, - {'name': 'zfs', 'version': 1002008, 'hypervisors': ['bhyve']}, - {'name': 'iscsi-direct', 'version': 4007000, 'hypervisors': ['kvm', 'xen']} - ] - - libvirt_version = conn.getLibVersion() - hypervisor = get_hypervisor() - - def _get_backend_output(backend): - output = { - 'name': backend['name'], - 'supported': (not backend.get('version') or libvirt_version >= backend['version']) and - hypervisor in backend.get('hypervisors', all_hypervisors), - 'options': { - 'pool': { - 'default_format': backend.get('default_source_format'), - 'sourceFormatType': backend.get('source_formats') - }, - 'volume': { - 'default_format': backend.get('default_target_format'), - 'targetFormatType': backend.get('target_formats') - } - } - } - - # Cleanup the empty members to match the libvirt output - for option_kind in ['pool', 'volume']: - if not [value for value in output['options'][option_kind].values() if value is not None]: - del output['options'][option_kind] - if not output['options']: - del output['options'] - - return output - pool_types = [_get_backend_output(backend) for backend in common_drivers] + return _pool_capabilities(conn) finally: conn.close() - return { - 'computed': not has_pool_capabilities, - 'pool_types': pool_types, - } - -def pool_define(name, - ptype, - target=None, - permissions=None, - source_devices=None, - source_dir=None, - source_initiator=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - transient=False, - start=True, # pylint: disable=redefined-outer-name - **kwargs): - ''' +def pool_define( + name, + ptype, + target=None, + permissions=None, + source_devices=None, + source_dir=None, + source_initiator=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + transient=False, + start=True, # pylint: disable=redefined-outer-name + **kwargs +): + """ Create libvirt pool. :param name: Pool name @@ -5065,7 +5803,7 @@ def pool_define(name, source_dir=samba_share source_hosts="['example.com']" target=/mnt/cifs .. versionadded:: 2019.2.0 - ''' + """ conn = __get_conn(**kwargs) auth = _pool_set_secret(conn, ptype, name, source_auth) @@ -5081,7 +5819,7 @@ def pool_define(name, source_auth=auth, source_name=source_name, source_format=source_format, - source_initiator=source_initiator + source_initiator=source_initiator, ) try: if transient: @@ -5090,7 +5828,7 @@ def pool_define(name, pool = conn.storagePoolDefineXML(pool_xml) if start: pool.create() - except libvirtError as err: + except libvirt.libvirtError as err: raise err # a real error we should report upwards finally: conn.close() @@ -5099,66 +5837,69 @@ def pool_define(name, return True -def _pool_set_secret(conn, pool_type, pool_name, source_auth, uuid=None, usage=None, test=False): - secret_types = { - 'rbd': 'ceph', - 'iscsi': 'chap', - 'iscsi-direct': 'chap' - } +def _pool_set_secret( + conn, pool_type, pool_name, source_auth, uuid=None, usage=None, test=False +): + secret_types = {"rbd": "ceph", "iscsi": "chap", "iscsi-direct": "chap"} secret_type = secret_types.get(pool_type) auth = source_auth - if source_auth and 'username' in source_auth and 'password' in source_auth: + if source_auth and "username" in source_auth and "password" in source_auth: if secret_type: # Get the previously defined secret if any secret = None if usage: - usage_type = libvirt.VIR_SECRET_USAGE_TYPE_CEPH if secret_type == 'ceph' \ - else libvirt.VIR_SECRET_USAGE_TYPE_ISCSI + usage_type = ( + libvirt.VIR_SECRET_USAGE_TYPE_CEPH + if secret_type == "ceph" + else libvirt.VIR_SECRET_USAGE_TYPE_ISCSI + ) secret = conn.secretLookupByUsage(usage_type, usage) elif uuid: secret = conn.secretLookupByUUIDString(uuid) # Create secret if needed if not secret: - description = 'Passphrase for {} pool created by Salt'.format(pool_name) + description = "Passphrase for {} pool created by Salt".format(pool_name) if not usage: - usage = 'pool_{}'.format(pool_name) + usage = "pool_{}".format(pool_name) secret_xml = _gen_secret_xml(secret_type, usage, description) if not test: secret = conn.secretDefineXML(secret_xml) # Assign the password to it - password = auth['password'] - if pool_type == 'rbd': + password = auth["password"] + if pool_type == "rbd": # RBD password are already base64-encoded, but libvirt will base64-encode them later password = base64.b64decode(salt.utils.stringutils.to_bytes(password)) if not test: secret.setValue(password) # update auth with secret reference - auth['type'] = secret_type - auth['secret'] = { - 'type': 'uuid' if uuid else 'usage', - 'value': uuid if uuid else usage, + auth["type"] = secret_type + auth["secret"] = { + "type": "uuid" if uuid else "usage", + "value": uuid if uuid else usage, } return auth -def pool_update(name, - ptype, - target=None, - permissions=None, - source_devices=None, - source_dir=None, - source_initiator=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - test=False, - **kwargs): - ''' +def pool_update( + name, + ptype, + target=None, + permissions=None, + source_devices=None, + source_dir=None, + source_initiator=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + test=False, + **kwargs +): + """ Update a libvirt storage pool if needed. If called with test=True, this is also reporting whether an update would be performed. @@ -5263,7 +6004,7 @@ def pool_update(name, source_dir=samba_share source_hosts="['example.com']" target=/mnt/cifs .. versionadded:: 3000 - ''' + """ # Get the current definition to compare the two conn = __get_conn(**kwargs) needs_update = False @@ -5273,29 +6014,33 @@ def pool_update(name, # If we have username and password in source_auth generate a new secret # Or change the value of the existing one - secret_node = old_xml.find('source/auth/secret') - usage = secret_node.get('usage') if secret_node is not None else None - uuid = secret_node.get('uuid') if secret_node is not None else None - auth = _pool_set_secret(conn, ptype, name, source_auth, uuid=uuid, usage=usage, test=test) + secret_node = old_xml.find("source/auth/secret") + usage = secret_node.get("usage") if secret_node is not None else None + uuid = secret_node.get("uuid") if secret_node is not None else None + auth = _pool_set_secret( + conn, ptype, name, source_auth, uuid=uuid, usage=usage, test=test + ) # Compute new definition - new_xml = ElementTree.fromstring(_gen_pool_xml( - name, - ptype, - target, - permissions=permissions, - source_devices=source_devices, - source_dir=source_dir, - source_initiator=source_initiator, - source_adapter=source_adapter, - source_hosts=source_hosts, - source_auth=auth, - source_name=source_name, - source_format=source_format - )) + new_xml = ElementTree.fromstring( + _gen_pool_xml( + name, + ptype, + target, + permissions=permissions, + source_devices=source_devices, + source_dir=source_dir, + source_initiator=source_initiator, + source_adapter=source_adapter, + source_hosts=source_hosts, + source_auth=auth, + source_name=source_name, + source_format=source_format, + ) + ) # Copy over the uuid, capacity, allocation, available elements - elements_to_copy = ['available', 'allocation', 'capacity', 'uuid'] + elements_to_copy = ["available", "allocation", "capacity", "uuid"] for to_copy in elements_to_copy: element = old_xml.find(to_copy) new_xml.insert(1, element) @@ -5308,29 +6053,37 @@ def pool_update(name, def space_stripper(node): if node.tail is not None: - node.tail = node.tail.strip(' \t\n') + node.tail = node.tail.strip(" \t\n") if node.text is not None: - node.text = node.text.strip(' \t\n') + node.text = node.text.strip(" \t\n") visit_xml(old_xml, space_stripper) visit_xml(new_xml, space_stripper) def empty_node_remover(node): for child in node: - if not child.tail and not child.text and not child.items() and not child: + if ( + not child.tail + and not child.text + and not child.items() + and not child + ): node.remove(child) + visit_xml(old_xml, empty_node_remover) - needs_update = ElementTree.tostring(old_xml) != ElementTree.tostring(new_xml) + needs_update = xmlutil.to_dict(old_xml, True) != xmlutil.to_dict(new_xml, True) if needs_update and not test: - conn.storagePoolDefineXML(salt.utils.stringutils.to_str(ElementTree.tostring(new_xml))) + conn.storagePoolDefineXML( + salt.utils.stringutils.to_str(ElementTree.tostring(new_xml)) + ) finally: conn.close() return needs_update def list_pools(**kwargs): - ''' + """ List all storage pools. :param connection: libvirt connection URI, overriding defaults @@ -5344,7 +6097,7 @@ def list_pools(**kwargs): .. code-block:: bash salt '*' virt.list_pools - ''' + """ conn = __get_conn(**kwargs) try: return [pool.name() for pool in conn.listAllStoragePools()] @@ -5353,7 +6106,7 @@ def list_pools(**kwargs): def pool_info(name=None, **kwargs): - ''' + """ Return informations on a storage pool provided its name. :param name: libvirt storage pool name @@ -5370,45 +6123,49 @@ def pool_info(name=None, **kwargs): .. code-block:: bash salt '*' virt.pool_info default - ''' + """ result = {} conn = __get_conn(**kwargs) def _pool_extract_infos(pool): - ''' + """ Format the pool info dictionary :param pool: the libvirt pool object - ''' - states = ['inactive', 'building', 'running', 'degraded', 'inaccessible'] + """ + states = ["inactive", "building", "running", "degraded", "inaccessible"] infos = pool.info() - state = states[infos[0]] if infos[0] < len(states) else 'unknown' + state = states[infos[0]] if infos[0] < len(states) else "unknown" desc = ElementTree.fromstring(pool.XMLDesc()) - path_node = desc.find('target/path') + path_node = desc.find("target/path") return { - 'uuid': pool.UUIDString(), - 'state': state, - 'capacity': infos[1], - 'allocation': infos[2], - 'free': infos[3], - 'autostart': pool.autostart(), - 'persistent': pool.isPersistent(), - 'target_path': path_node.text if path_node is not None else None, - 'type': desc.get('type') + "uuid": pool.UUIDString(), + "state": state, + "capacity": infos[1], + "allocation": infos[2], + "free": infos[3], + "autostart": pool.autostart(), + "persistent": pool.isPersistent(), + "target_path": path_node.text if path_node is not None else None, + "type": desc.get("type"), } try: - pools = [pool for pool in conn.listAllStoragePools() if name is None or pool.name() == name] + pools = [ + pool + for pool in conn.listAllStoragePools() + if name is None or pool.name() == name + ] result = {pool.name(): _pool_extract_infos(pool) for pool in pools} except libvirt.libvirtError as err: - log.debug('Silenced libvirt error: %s', str(err)) + log.debug("Silenced libvirt error: %s", str(err)) finally: conn.close() return result def pool_get_xml(name, **kwargs): - ''' + """ Return the XML definition of a virtual storage pool :param name: libvirt storage pool name @@ -5423,7 +6180,7 @@ def pool_get_xml(name, **kwargs): .. code-block:: bash salt '*' virt.pool_get_xml default - ''' + """ conn = __get_conn(**kwargs) try: return conn.storagePoolLookupByName(name).XMLDesc() @@ -5432,7 +6189,7 @@ def pool_get_xml(name, **kwargs): def pool_start(name, **kwargs): - ''' + """ Start a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5447,7 +6204,7 @@ def pool_start(name, **kwargs): .. code-block:: bash salt '*' virt.pool_start default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5457,7 +6214,7 @@ def pool_start(name, **kwargs): def pool_build(name, **kwargs): - ''' + """ Build a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5472,7 +6229,7 @@ def pool_build(name, **kwargs): .. code-block:: bash salt '*' virt.pool_build default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5482,7 +6239,7 @@ def pool_build(name, **kwargs): def pool_stop(name, **kwargs): - ''' + """ Stop a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5497,7 +6254,7 @@ def pool_stop(name, **kwargs): .. code-block:: bash salt '*' virt.pool_stop default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5507,7 +6264,7 @@ def pool_stop(name, **kwargs): def pool_undefine(name, **kwargs): - ''' + """ Remove a defined libvirt storage pool. The pool needs to be stopped before calling. :param name: libvirt storage pool name @@ -5522,7 +6279,7 @@ def pool_undefine(name, **kwargs): .. code-block:: bash salt '*' virt.pool_undefine default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5532,7 +6289,7 @@ def pool_undefine(name, **kwargs): def pool_delete(name, **kwargs): - ''' + """ Delete the resources of a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5547,17 +6304,33 @@ def pool_delete(name, **kwargs): .. code-block:: bash salt '*' virt.pool_delete default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) + desc = ElementTree.fromstring(pool.XMLDesc()) + + # Is there a secret that we generated and would need to be removed? + # Don't remove the other secrets + auth_node = desc.find("source/auth") + if auth_node is not None: + auth_types = { + "ceph": libvirt.VIR_SECRET_USAGE_TYPE_CEPH, + "iscsi": libvirt.VIR_SECRET_USAGE_TYPE_ISCSI, + } + secret_type = auth_types[auth_node.get("type")] + secret_usage = auth_node.find("secret").get("usage") + if secret_type and "pool_{}".format(name) == secret_usage: + secret = conn.secretLookupByUsage(secret_type, secret_usage) + secret.undefine() + return not bool(pool.delete(libvirt.VIR_STORAGE_POOL_DELETE_NORMAL)) finally: conn.close() def pool_refresh(name, **kwargs): - ''' + """ Refresh a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5572,7 +6345,7 @@ def pool_refresh(name, **kwargs): .. code-block:: bash salt '*' virt.pool_refresh default - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5581,8 +6354,8 @@ def pool_refresh(name, **kwargs): conn.close() -def pool_set_autostart(name, state='on', **kwargs): - ''' +def pool_set_autostart(name, state="on", **kwargs): + """ Set the autostart flag on a libvirt storage pool so that the storage pool will start with the host system on reboot. @@ -5600,17 +6373,17 @@ def pool_set_autostart(name, state='on', **kwargs): .. code-block:: bash salt "*" virt.pool_set_autostart <pool> <on | off> - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) - return not bool(pool.setAutostart(1 if state == 'on' else 0)) + return not bool(pool.setAutostart(1 if state == "on" else 0)) finally: conn.close() def pool_list_volumes(name, **kwargs): - ''' + """ List the volumes contained in a defined libvirt storage pool. :param name: libvirt storage pool name @@ -5625,7 +6398,7 @@ def pool_list_volumes(name, **kwargs): .. code-block:: bash salt "*" virt.pool_list_volumes <pool> - ''' + """ conn = __get_conn(**kwargs) try: pool = conn.storagePoolLookupByName(name) @@ -5635,27 +6408,28 @@ def pool_list_volumes(name, **kwargs): def _get_storage_vol(conn, pool, vol): - ''' + """ Helper function getting a storage volume. Will throw a libvirtError if the pool or the volume couldn't be found. :param conn: libvirt connection object to use :param pool: pool name :param vol: volume name - ''' + """ pool_obj = conn.storagePoolLookupByName(pool) return pool_obj.storageVolLookupByName(vol) def _is_valid_volume(vol): - ''' + """ Checks whether a volume is valid for further use since those may have disappeared since the last pool refresh. - ''' + """ try: # Getting info on an invalid volume raises error and libvirt logs an error def discarder(ctxt, error): # pylint: disable=unused-argument log.debug("Ignore libvirt error: %s", error[2]) + # Disable the libvirt error logging libvirt.registerErrorHandler(discarder, None) vol.info() @@ -5667,20 +6441,34 @@ def _is_valid_volume(vol): def _get_all_volumes_paths(conn): - ''' + """ Extract the path and backing stores path of all volumes. :param conn: libvirt connection to use - ''' - volumes = [vol for l in - [obj.listAllVolumes() for obj in conn.listAllStoragePools() - if obj.info()[0] == libvirt.VIR_STORAGE_POOL_RUNNING] for vol in l] - return {vol.path(): [path.text for path in ElementTree.fromstring(vol.XMLDesc()).findall('.//backingStore/path')] - for vol in volumes if _is_valid_volume(vol)} + """ + volumes = [ + vol + for l in [ + obj.listAllVolumes() + for obj in conn.listAllStoragePools() + if obj.info()[0] == libvirt.VIR_STORAGE_POOL_RUNNING + ] + for vol in l + ] + return { + vol.path(): [ + path.text + for path in ElementTree.fromstring(vol.XMLDesc()).findall( + ".//backingStore/path" + ) + ] + for vol in volumes + if _is_valid_volume(vol) + } def volume_infos(pool=None, volume=None, **kwargs): - ''' + """ Provide details on a storage volume. If no volume name is provided, the infos all the volumes contained in the pool are provided. If no pool is provided, the infos of the volumes of all pools are output. @@ -5698,7 +6486,7 @@ def volume_infos(pool=None, volume=None, **kwargs): .. code-block:: bash salt "*" virt.volume_infos <pool> <volume> - ''' + """ result = {} conn = __get_conn(**kwargs) try: @@ -5709,52 +6497,88 @@ def volume_infos(pool=None, volume=None, **kwargs): except CommandExecutionError: # Having no VM is not an error here. domains_list = [] - disks = {domain.name(): - {node.get('file') for node - in ElementTree.fromstring(domain.XMLDesc(0)).findall('.//disk/source/[@file]')} - for domain in domains_list} + disks = { + domain.name(): { + node.get("file") + for node in ElementTree.fromstring(domain.XMLDesc(0)).findall( + ".//disk/source/[@file]" + ) + } + for domain in domains_list + } def _volume_extract_infos(vol): - ''' + """ Format the volume info dictionary :param vol: the libvirt storage volume object. - ''' - types = ['file', 'block', 'dir', 'network', 'netdir', 'ploop'] + """ + types = ["file", "block", "dir", "network", "netdir", "ploop"] infos = vol.info() + vol_xml = ElementTree.fromstring(vol.XMLDesc()) + backing_store_path = vol_xml.find("./backingStore/path") + backing_store_format = vol_xml.find("./backingStore/format") + backing_store = None + if backing_store_path is not None: + backing_store = { + "path": backing_store_path.text, + "format": backing_store_format.get("type") + if backing_store_format is not None + else None, + } + + format_node = vol_xml.find("./target/format") + # If we have a path, check its use. used_by = [] if vol.path(): - as_backing_store = {path for (path, all_paths) in backing_stores.items() if vol.path() in all_paths} - used_by = [vm_name for (vm_name, vm_disks) in disks.items() - if vm_disks & as_backing_store or vol.path() in vm_disks] + as_backing_store = { + path + for (path, all_paths) in backing_stores.items() + if vol.path() in all_paths + } + used_by = [ + vm_name + for (vm_name, vm_disks) in disks.items() + if vm_disks & as_backing_store or vol.path() in vm_disks + ] return { - 'type': types[infos[0]] if infos[0] < len(types) else 'unknown', - 'key': vol.key(), - 'path': vol.path(), - 'capacity': infos[1], - 'allocation': infos[2], - 'used_by': used_by, + "type": types[infos[0]] if infos[0] < len(types) else "unknown", + "key": vol.key(), + "path": vol.path(), + "capacity": infos[1], + "allocation": infos[2], + "used_by": used_by, + "backing_store": backing_store, + "format": format_node.get("type") if format_node is not None else None, } - pools = [obj for obj in conn.listAllStoragePools() - if (pool is None or obj.name() == pool) and obj.info()[0] == libvirt.VIR_STORAGE_POOL_RUNNING] - vols = {pool_obj.name(): {vol.name(): _volume_extract_infos(vol) - for vol in pool_obj.listAllVolumes() - if (volume is None or vol.name() == volume) and _is_valid_volume(vol)} - for pool_obj in pools} + pools = [ + obj + for obj in conn.listAllStoragePools() + if (pool is None or obj.name() == pool) + and obj.info()[0] == libvirt.VIR_STORAGE_POOL_RUNNING + ] + vols = { + pool_obj.name(): { + vol.name(): _volume_extract_infos(vol) + for vol in pool_obj.listAllVolumes() + if (volume is None or vol.name() == volume) and _is_valid_volume(vol) + } + for pool_obj in pools + } return {pool_name: volumes for (pool_name, volumes) in vols.items() if volumes} except libvirt.libvirtError as err: - log.debug('Silenced libvirt error: %s', str(err)) + log.debug("Silenced libvirt error: %s", str(err)) finally: conn.close() return result def volume_delete(pool, volume, **kwargs): - ''' + """ Delete a libvirt managed volume. :param pool: libvirt storage pool name @@ -5770,10 +6594,222 @@ def volume_delete(pool, volume, **kwargs): .. code-block:: bash salt "*" virt.volume_delete <pool> <volume> - ''' + """ conn = __get_conn(**kwargs) try: vol = _get_storage_vol(conn, pool, volume) return not bool(vol.delete()) finally: conn.close() + + +def volume_define( + pool, + name, + size, + allocation=0, + format=None, + type=None, + permissions=None, + backing_store=None, + nocow=False, + **kwargs +): + """ + Create libvirt volume. + + :param pool: name of the pool to create the volume in + :param name: name of the volume to define + :param size: capacity of the volume to define in MiB + :param allocation: allocated size of the volume in MiB. Defaults to 0. + :param format: + volume format. The allowed values are depending on the pool type. + Check the virt.pool_capabilities output for the possible values and the default. + :param type: + type of the volume. One of file, block, dir, network, netdiri, ploop or None. + By default, the type is guessed by libvirt from the pool type. + :param permissions: + Permissions to set on the target folder. This is mostly used for filesystem-based + pool types. See :ref:`pool-define-permissions` for more details on this structure. + :param backing_store: + dictionary describing a backing file for the volume. It must contain a ``path`` + property pointing to the base volume and a ``format`` property defining the format + of the base volume. + + The base volume format will not be guessed for security reasons and is thus mandatory. + :param nocow: disable COW for the volume. + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + .. rubric:: CLI Example: + + Volume on ESX: + + .. code-block:: bash + + salt '*' virt.volume_define "[local-storage]" myvm/myvm.vmdk vmdk 8192 + + QCow2 volume with backing file: + + .. code-block:: bash + + salt '*' virt.volume_define default myvm.qcow2 qcow2 8192 \ + permissions="{'mode': '0775', 'owner': '123', 'group': '345'"}" \ + backing_store="{'path': '/path/to/base.img', 'format': 'raw'}" \ + nocow=True + + .. versionadded:: Sodium + """ + ret = False + try: + conn = __get_conn(**kwargs) + pool_obj = conn.storagePoolLookupByName(pool) + pool_type = ElementTree.fromstring(pool_obj.XMLDesc()).get("type") + new_allocation = allocation + if pool_type == "logical" and size != allocation: + new_allocation = size + xml = _gen_vol_xml( + name, + size, + format=format, + allocation=new_allocation, + type=type, + permissions=permissions, + backing_store=backing_store, + nocow=nocow, + ) + ret = _define_vol_xml_str(conn, xml, pool=pool) + except libvirt.libvirtError as err: + raise CommandExecutionError(err.get_error_message()) + finally: + conn.close() + return ret + + +def _volume_upload(conn, pool, volume, file, offset=0, length=0, sparse=False): + """ + Function performing the heavy duty for volume_upload but using an already + opened libvirt connection. + """ + + def handler(stream, nbytes, opaque): + return os.read(opaque, nbytes) + + def holeHandler(stream, opaque): + """ + Taken from the sparsestream.py libvirt-python example. + """ + fd = opaque + cur = os.lseek(fd, 0, os.SEEK_CUR) + + try: + data = os.lseek(fd, cur, os.SEEK_DATA) + except OSError as e: + if e.errno != 6: + raise e + else: + data = -1 + if data < 0: + inData = False + eof = os.lseek(fd, 0, os.SEEK_END) + if eof < cur: + raise RuntimeError("Current position in file after EOF: {}".format(cur)) + sectionLen = eof - cur + else: + if data > cur: + inData = False + sectionLen = data - cur + else: + inData = True + + hole = os.lseek(fd, data, os.SEEK_HOLE) + if hole < 0: + raise RuntimeError("No trailing hole") + + if hole == data: + raise RuntimeError("Impossible happened") + else: + sectionLen = hole - data + os.lseek(fd, cur, os.SEEK_SET) + return [inData, sectionLen] + + def skipHandler(stream, length, opaque): + return os.lseek(opaque, length, os.SEEK_CUR) + + stream = None + fd = None + ret = False + try: + pool_obj = conn.storagePoolLookupByName(pool) + vol_obj = pool_obj.storageVolLookupByName(volume) + + stream = conn.newStream() + fd = os.open(file, os.O_RDONLY) + vol_obj.upload( + stream, + offset, + length, + libvirt.VIR_STORAGE_VOL_UPLOAD_SPARSE_STREAM if sparse else 0, + ) + if sparse: + stream.sparseSendAll(handler, holeHandler, skipHandler, fd) + else: + stream.sendAll(handler, fd) + ret = True + except libvirt.libvirtError as err: + raise CommandExecutionError(err.get_error_message()) + finally: + if fd: + try: + os.close(fd) + except OSError as err: + if stream: + stream.abort() + if ret: + raise CommandExecutionError( + "Failed to close file: {0}".format(err.strerror) + ) + if stream: + try: + stream.finish() + except libvirt.libvirtError as err: + if ret: + raise CommandExecutionError( + "Failed to finish stream: {0}".format(err.get_error_message()) + ) + return ret + + +def volume_upload(pool, volume, file, offset=0, length=0, sparse=False, **kwargs): + """ + Create libvirt volume. + + :param pool: name of the pool to create the volume in + :param name: name of the volume to define + :param file: the file to upload to the volume + :param offset: where to start writing the data in the volume + :param length: amount of bytes to transfer to the volume + :param sparse: set to True to preserve data sparsiness. + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + .. rubric:: CLI Example: + + .. code-block:: bash + + salt '*' virt.volume_upload default myvm.qcow2 /path/to/disk.qcow2 + + .. versionadded:: Sodium + """ + conn = __get_conn(**kwargs) + + ret = False + try: + ret = _volume_upload( + conn, pool, volume, file, offset=offset, length=length, sparse=sparse + ) + finally: + conn.close() + return ret diff --git a/salt/states/virt.py b/salt/states/virt.py index 819776d707..fdef002293 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -''' +""" Manage virt =========== @@ -10,47 +10,49 @@ for the generation and signing of certificates for systems running libvirt: libvirt_keys: virt.keys -''' +""" # Import Python libs from __future__ import absolute_import, print_function, unicode_literals -import copy + import fnmatch import os -try: - import libvirt # pylint: disable=import-error - HAS_LIBVIRT = True -except ImportError: - HAS_LIBVIRT = False - # Import Salt libs import salt.utils.args import salt.utils.files import salt.utils.stringutils import salt.utils.versions -from salt.exceptions import CommandExecutionError +from salt.exceptions import CommandExecutionError, SaltInvocationError # Import 3rd-party libs from salt.ext import six -__virtualname__ = 'virt' +try: + import libvirt # pylint: disable=import-error + + HAS_LIBVIRT = True +except ImportError: + HAS_LIBVIRT = False + + +__virtualname__ = "virt" def __virtual__(): - ''' + """ Only if virt module is available. :return: - ''' + """ - if 'virt.node_info' in __salt__: + if "virt.node_info" in __salt__: return __virtualname__ return False -def keys(name, basepath='/etc/pki', **kwargs): - ''' +def keys(name, basepath="/etc/pki", **kwargs): + """ Manage libvirt keys. name @@ -90,65 +92,68 @@ def keys(name, basepath='/etc/pki', **kwargs): .. versionadded:: 2018.3.0 - ''' - ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''} + """ + ret = {"name": name, "changes": {}, "result": True, "comment": ""} # Grab all kwargs to make them available as pillar values # rename them to something hopefully unique to avoid # overriding anything existing pillar_kwargs = {} for key, value in six.iteritems(kwargs): - pillar_kwargs['ext_pillar_virt.{0}'.format(key)] = value + pillar_kwargs["ext_pillar_virt.{0}".format(key)] = value - pillar = __salt__['pillar.ext']({'libvirt': '_'}, pillar_kwargs) + pillar = __salt__["pillar.ext"]({"libvirt": "_"}, pillar_kwargs) paths = { - 'serverkey': os.path.join(basepath, 'libvirt', - 'private', 'serverkey.pem'), - 'servercert': os.path.join(basepath, 'libvirt', - 'servercert.pem'), - 'clientkey': os.path.join(basepath, 'libvirt', - 'private', 'clientkey.pem'), - 'clientcert': os.path.join(basepath, 'libvirt', - 'clientcert.pem'), - 'cacert': os.path.join(basepath, 'CA', 'cacert.pem') + "serverkey": os.path.join(basepath, "libvirt", "private", "serverkey.pem"), + "servercert": os.path.join(basepath, "libvirt", "servercert.pem"), + "clientkey": os.path.join(basepath, "libvirt", "private", "clientkey.pem"), + "clientcert": os.path.join(basepath, "libvirt", "clientcert.pem"), + "cacert": os.path.join(basepath, "CA", "cacert.pem"), } for key in paths: - p_key = 'libvirt.{0}.pem'.format(key) + p_key = "libvirt.{0}.pem".format(key) if p_key not in pillar: continue if not os.path.exists(os.path.dirname(paths[key])): os.makedirs(os.path.dirname(paths[key])) if os.path.isfile(paths[key]): - with salt.utils.files.fopen(paths[key], 'r') as fp_: + with salt.utils.files.fopen(paths[key], "r") as fp_: if salt.utils.stringutils.to_unicode(fp_.read()) != pillar[p_key]: - ret['changes'][key] = 'update' + ret["changes"][key] = "update" else: - ret['changes'][key] = 'new' - - if not ret['changes']: - ret['comment'] = 'All keys are correct' - elif __opts__['test']: - ret['result'] = None - ret['comment'] = 'Libvirt keys are set to be updated' - ret['changes'] = {} + ret["changes"][key] = "new" + + if not ret["changes"]: + ret["comment"] = "All keys are correct" + elif __opts__["test"]: + ret["result"] = None + ret["comment"] = "Libvirt keys are set to be updated" + ret["changes"] = {} else: - for key in ret['changes']: - with salt.utils.files.fopen(paths[key], 'w+') as fp_: + for key in ret["changes"]: + with salt.utils.files.fopen(paths[key], "w+") as fp_: fp_.write( - salt.utils.stringutils.to_str( - pillar['libvirt.{0}.pem'.format(key)] - ) + salt.utils.stringutils.to_str(pillar["libvirt.{0}.pem".format(key)]) ) - ret['comment'] = 'Updated libvirt certs and keys' + ret["comment"] = "Updated libvirt certs and keys" return ret -def _virt_call(domain, function, section, comment, state=None, - connection=None, username=None, password=None, **kwargs): - ''' +def _virt_call( + domain, + function, + section, + comment, + state=None, + connection=None, + username=None, + password=None, + **kwargs +): + """ Helper to call the virt functions. Wildcards supported. :param domain: the domain to apply the function on. Can contain wildcards. @@ -157,9 +162,9 @@ def _virt_call(domain, function, section, comment, state=None, :param comment: comment to return :param state: the expected final state of the VM. If None the VM state won't be checked. :return: the salt state return - ''' - ret = {'name': domain, 'changes': {}, 'result': True, 'comment': ''} - targeted_domains = fnmatch.filter(__salt__['virt.list_domains'](), domain) + """ + ret = {"name": domain, "changes": {}, "result": True, "comment": ""} + targeted_domains = fnmatch.filter(__salt__["virt.list_domains"](), domain) changed_domains = list() ignored_domains = list() noaction_domains = list() @@ -168,35 +173,39 @@ def _virt_call(domain, function, section, comment, state=None, action_needed = True # If a state has been provided, use it to see if we have something to do if state is not None: - domain_state = __salt__['virt.vm_state'](targeted_domain) + domain_state = __salt__["virt.vm_state"](targeted_domain) action_needed = domain_state.get(targeted_domain) != state if action_needed: - response = __salt__['virt.{0}'.format(function)](targeted_domain, - connection=connection, - username=username, - password=password, - **kwargs) + response = __salt__["virt.{0}".format(function)]( + targeted_domain, + connection=connection, + username=username, + password=password, + **kwargs + ) if isinstance(response, dict): - response = response['name'] - changed_domains.append({'domain': targeted_domain, function: response}) + response = response["name"] + changed_domains.append({"domain": targeted_domain, function: response}) else: noaction_domains.append(targeted_domain) except libvirt.libvirtError as err: - ignored_domains.append({'domain': targeted_domain, 'issue': six.text_type(err)}) + ignored_domains.append( + {"domain": targeted_domain, "issue": six.text_type(err)} + ) if not changed_domains: - ret['result'] = not ignored_domains and bool(targeted_domains) - ret['comment'] = 'No changes had happened' + ret["result"] = not ignored_domains and bool(targeted_domains) + ret["comment"] = "No changes had happened" if ignored_domains: - ret['changes'] = {'ignored': ignored_domains} + ret["changes"] = {"ignored": ignored_domains} else: - ret['changes'] = {section: changed_domains} - ret['comment'] = comment + ret["changes"] = {section: changed_domains} + ret["comment"] = comment return ret def stopped(name, connection=None, username=None, password=None): - ''' + """ Stops a VM by shutting it down nicely. .. versionadded:: 2016.3.0 @@ -215,14 +224,22 @@ def stopped(name, connection=None, username=None, password=None): domain_name: virt.stopped - ''' + """ - return _virt_call(name, 'shutdown', 'stopped', 'Machine has been shut down', state='shutdown', - connection=connection, username=username, password=password) + return _virt_call( + name, + "shutdown", + "stopped", + "Machine has been shut down", + state="shutdown", + connection=connection, + username=username, + password=password, + ) def powered_off(name, connection=None, username=None, password=None): - ''' + """ Stops a VM by power off. .. versionadded:: 2016.3.0 @@ -241,32 +258,42 @@ def powered_off(name, connection=None, username=None, password=None): domain_name: virt.stopped - ''' - return _virt_call(name, 'stop', 'unpowered', 'Machine has been powered off', state='shutdown', - connection=connection, username=username, password=password) - - -def defined(name, - cpu=None, - mem=None, - vm_type=None, - disk_profile=None, - disks=None, - nic_profile=None, - interfaces=None, - graphics=None, - seed=True, - install=True, - pub_key=None, - priv_key=None, - connection=None, - username=None, - password=None, - os_type=None, - arch=None, - boot=None, - update=True): - ''' + """ + return _virt_call( + name, + "stop", + "unpowered", + "Machine has been powered off", + state="shutdown", + connection=connection, + username=username, + password=password, + ) + + +def defined( + name, + cpu=None, + mem=None, + vm_type=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + seed=True, + install=True, + pub_key=None, + priv_key=None, + connection=None, + username=None, + password=None, + os_type=None, + arch=None, + boot=None, + update=True, +): + """ Starts an existing guest, or defines and starts a new VM with specified arguments. .. versionadded:: sodium @@ -311,19 +338,15 @@ def defined(name, but ``x86_64`` is prefed over ``i686``. Only used when creating a new virtual machine. :param boot: - Specifies kernel for the virtual machine, as well as boot parameters - for the virtual machine. This is an optionl parameter, and all of the - keys are optional within the dictionary. If a remote path is provided - to kernel or initrd, salt will handle the downloading of the specified - remote fild, and will modify the XML accordingly. + Specifies kernel, initial ramdisk and kernel command line parameters for the virtual machine. + This is an optional parameter, all of the keys are optional within the dictionary. - .. code-block:: python + Refer to :ref:`init-boot-def` for the complete boot parameters description. - { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' - } + To update any boot parameters, specify the new path for each. To remove any boot parameters, + pass a None object, for instance: 'kernel': ``None``. + + .. versionadded:: 3000 :param update: set to ``False`` to prevent updating a defined domain. (Default: ``True``) @@ -361,94 +384,104 @@ def defined(name, type: address address: 192.168.0.125 - ''' + """ - ret = {'name': name, - 'changes': {}, - 'result': True if not __opts__['test'] else None, - 'comment': '' - } + ret = { + "name": name, + "changes": {}, + "result": True if not __opts__["test"] else None, + "comment": "", + } try: - if name in __salt__['virt.list_domains'](connection=connection, username=username, password=password): + if name in __salt__["virt.list_domains"]( + connection=connection, username=username, password=password + ): status = {} if update: - status = __salt__['virt.update'](name, - cpu=cpu, - mem=mem, - disk_profile=disk_profile, - disks=disks, - nic_profile=nic_profile, - interfaces=interfaces, - graphics=graphics, - live=True, - connection=connection, - username=username, - password=password, - boot=boot, - test=__opts__['test']) - ret['changes'][name] = status - if not status.get('definition'): - ret['comment'] = 'Domain {0} unchanged'.format(name) - ret['result'] = True - elif status.get('errors'): - ret['comment'] = 'Domain {0} updated with live update(s) failures'.format(name) + status = __salt__["virt.update"]( + name, + cpu=cpu, + mem=mem, + disk_profile=disk_profile, + disks=disks, + nic_profile=nic_profile, + interfaces=interfaces, + graphics=graphics, + live=True, + connection=connection, + username=username, + password=password, + boot=boot, + test=__opts__["test"], + ) + ret["changes"][name] = status + if not status.get("definition"): + ret["comment"] = "Domain {0} unchanged".format(name) + ret["result"] = True + elif status.get("errors"): + ret[ + "comment" + ] = "Domain {0} updated with live update(s) failures".format(name) else: - ret['comment'] = 'Domain {0} updated'.format(name) + ret["comment"] = "Domain {0} updated".format(name) else: - if not __opts__['test']: - __salt__['virt.init'](name, - cpu=cpu, - mem=mem, - os_type=os_type, - arch=arch, - hypervisor=vm_type, - disk=disk_profile, - disks=disks, - nic=nic_profile, - interfaces=interfaces, - graphics=graphics, - seed=seed, - install=install, - pub_key=pub_key, - priv_key=priv_key, - connection=connection, - username=username, - password=password, - boot=boot, - start=False) - ret['changes'][name] = {'definition': True} - ret['comment'] = 'Domain {0} defined'.format(name) + if not __opts__["test"]: + __salt__["virt.init"]( + name, + cpu=cpu, + mem=mem, + os_type=os_type, + arch=arch, + hypervisor=vm_type, + disk=disk_profile, + disks=disks, + nic=nic_profile, + interfaces=interfaces, + graphics=graphics, + seed=seed, + install=install, + pub_key=pub_key, + priv_key=priv_key, + connection=connection, + username=username, + password=password, + boot=boot, + start=False, + ) + ret["changes"][name] = {"definition": True} + ret["comment"] = "Domain {0} defined".format(name) except libvirt.libvirtError as err: # Something bad happened when defining / updating the VM, report it - ret['comment'] = six.text_type(err) - ret['result'] = False + ret["comment"] = six.text_type(err) + ret["result"] = False return ret -def running(name, - cpu=None, - mem=None, - image=None, - vm_type=None, - disk_profile=None, - disks=None, - nic_profile=None, - interfaces=None, - graphics=None, - seed=True, - install=True, - pub_key=None, - priv_key=None, - update=False, - connection=None, - username=None, - password=None, - os_type=None, - arch=None, - boot=None): - ''' +def running( + name, + cpu=None, + mem=None, + vm_type=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + seed=True, + install=True, + pub_key=None, + priv_key=None, + update=False, + connection=None, + username=None, + password=None, + os_type=None, + arch=None, + boot=None, +): + """ Starts an existing guest, or defines and starts a new VM with specified arguments. .. versionadded:: 2016.3.0 @@ -456,9 +489,6 @@ def running(name, :param name: name of the virtual machine to run :param cpu: number of CPUs for the virtual machine to create :param mem: amount of memory in MiB for the new virtual machine - :param image: disk image to use for the first disk of the new VM - - .. deprecated:: 2019.2.0 :param vm_type: force virtual machine type for the new VM. The default value is taken from the host capabilities. This could be useful for example to use ``'qemu'`` type instead of the ``'kvm'`` one. @@ -534,19 +564,13 @@ def running(name, .. versionadded:: 3000 :param boot: - Specifies kernel for the virtual machine, as well as boot parameters - for the virtual machine. This is an optionl parameter, and all of the - keys are optional within the dictionary. If a remote path is provided - to kernel or initrd, salt will handle the downloading of the specified - remote fild, and will modify the XML accordingly. + Specifies kernel, initial ramdisk and kernel command line parameters for the virtual machine. + This is an optional parameter, all of the keys are optional within the dictionary. - .. code-block:: python + Refer to :ref:`init-boot-def` for the complete boot parameters description. - { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' - } + To update any boot parameters, specify the new path for each. To remove any boot parameters, + pass a None object, for instance: 'kernel': ``None``. .. versionadded:: 3000 @@ -606,81 +630,69 @@ def running(name, type: address address: 192.168.0.125 - ''' + """ merged_disks = disks - if image: - default_disks = [{'system': {}}] - disknames = ['system'] - if disk_profile: - disklist = copy.deepcopy( - __salt__['config.get']('virt:disk', {}).get(disk_profile, default_disks)) - disknames = disklist.keys() - disk = {'name': disknames[0], 'image': image} - if merged_disks: - first_disk = [d for d in merged_disks if d.get('name') == disknames[0]] - if first_disk and 'image' not in first_disk[0]: - first_disk[0]['image'] = image - else: - merged_disks.append(disk) - else: - merged_disks = [disk] - salt.utils.versions.warn_until( - 'Sodium', - '\'image\' parameter has been deprecated. Rather use the \'disks\' parameter ' - 'to override or define the image. \'image\' will be removed in {version}.' - ) if not update: - salt.utils.versions.warn_until('Magnesium', - '\'update\' parameter has been deprecated. Future behavior will be the one of update=True' - 'It will be removed in {version}.') - ret = defined(name, - cpu=cpu, - mem=mem, - vm_type=vm_type, - disk_profile=disk_profile, - disks=merged_disks, - nic_profile=nic_profile, - interfaces=interfaces, - graphics=graphics, - seed=seed, - install=install, - pub_key=pub_key, - priv_key=priv_key, - os_type=os_type, - arch=arch, - boot=boot, - update=update, - connection=connection, - username=username, - password=password) - - result = True if not __opts__['test'] else None - if ret['result'] is None or ret['result']: - changed = ret['changes'][name].get('definition', False) + salt.utils.versions.warn_until( + "Aluminium", + "'update' parameter has been deprecated. Future behavior will be the one of update=True" + "It will be removed in {version}.", + ) + ret = defined( + name, + cpu=cpu, + mem=mem, + vm_type=vm_type, + disk_profile=disk_profile, + disks=merged_disks, + nic_profile=nic_profile, + interfaces=interfaces, + graphics=graphics, + seed=seed, + install=install, + pub_key=pub_key, + priv_key=priv_key, + os_type=os_type, + arch=arch, + boot=boot, + update=update, + connection=connection, + username=username, + password=password, + ) + + result = True if not __opts__["test"] else None + if ret["result"] is None or ret["result"]: + changed = ret["changes"][name].get("definition", False) try: - domain_state = __salt__['virt.vm_state'](name) - if domain_state.get(name) != 'running': - if not __opts__['test']: - __salt__['virt.start'](name, connection=connection, username=username, password=password) - comment = 'Domain {} started'.format(name) - if not ret['comment'].endswith('unchanged'): - comment = '{} and started'.format(ret['comment']) - ret['comment'] = comment - ret['changes'][name]['started'] = True + domain_state = __salt__["virt.vm_state"](name) + if domain_state.get(name) != "running": + if not __opts__["test"]: + __salt__["virt.start"]( + name, + connection=connection, + username=username, + password=password, + ) + comment = "Domain {} started".format(name) + if not ret["comment"].endswith("unchanged"): + comment = "{} and started".format(ret["comment"]) + ret["comment"] = comment + ret["changes"][name]["started"] = True elif not changed: - ret['comment'] = 'Domain {0} exists and is running'.format(name) + ret["comment"] = "Domain {0} exists and is running".format(name) except libvirt.libvirtError as err: # Something bad happened when starting / updating the VM, report it - ret['comment'] = six.text_type(err) - ret['result'] = False + ret["comment"] = six.text_type(err) + ret["result"] = False return ret def snapshot(name, suffix=None, connection=None, username=None, password=None): - ''' + """ Takes a snapshot of a particular VM or by a UNIX-style wildcard. .. versionadded:: 2016.3.0 @@ -704,15 +716,23 @@ def snapshot(name, suffix=None, connection=None, username=None, password=None): domain*: virt.snapshot: - suffix: periodic - ''' + """ - return _virt_call(name, 'snapshot', 'saved', 'Snapshot has been taken', suffix=suffix, - connection=connection, username=username, password=password) + return _virt_call( + name, + "snapshot", + "saved", + "Snapshot has been taken", + suffix=suffix, + connection=connection, + username=username, + password=password, + ) # Deprecated states def rebooted(name, connection=None, username=None, password=None): - ''' + """ Reboots VMs .. versionadded:: 2016.3.0 @@ -728,14 +748,21 @@ def rebooted(name, connection=None, username=None, password=None): :param password: password to connect with, overriding defaults .. versionadded:: 2019.2.0 - ''' + """ - return _virt_call(name, 'reboot', 'rebooted', "Machine has been rebooted", - connection=connection, username=username, password=password) + return _virt_call( + name, + "reboot", + "rebooted", + "Machine has been rebooted", + connection=connection, + username=username, + password=password, + ) def unpowered(name): - ''' + """ .. deprecated:: 2016.3.0 Use :py:func:`~salt.modules.virt.powered_off` instead. @@ -747,13 +774,13 @@ def unpowered(name): domain_name: virt.stopped - ''' + """ - return _virt_call(name, 'stop', 'unpowered', 'Machine has been powered off') + return _virt_call(name, "stop", "unpowered", "Machine has been powered off") def saved(name, suffix=None): - ''' + """ .. deprecated:: 2016.3.0 Use :py:func:`~salt.modules.virt.snapshot` instead. @@ -770,13 +797,17 @@ def saved(name, suffix=None): domain*: virt.saved: - suffix: periodic - ''' + """ - return _virt_call(name, 'snapshot', 'saved', 'Snapshots has been taken', suffix=suffix) + return _virt_call( + name, "snapshot", "saved", "Snapshots has been taken", suffix=suffix + ) -def reverted(name, snapshot=None, cleanup=False): # pylint: disable=redefined-outer-name - ''' +def reverted( + name, snapshot=None, cleanup=False +): # pylint: disable=redefined-outer-name + """ .. deprecated:: 2016.3.0 Reverts to the particular snapshot. @@ -793,59 +824,71 @@ def reverted(name, snapshot=None, cleanup=False): # pylint: disable=redefined-o virt.reverted: - snapshot: snapshot_name - cleanup: False - ''' - ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''} + """ + ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: - domains = fnmatch.filter(__salt__['virt.list_domains'](), name) + domains = fnmatch.filter(__salt__["virt.list_domains"](), name) if not domains: - ret['comment'] = 'No domains found for criteria "{0}"'.format(name) + ret["comment"] = 'No domains found for criteria "{0}"'.format(name) else: ignored_domains = list() if len(domains) > 1: - ret['changes'] = {'reverted': list()} + ret["changes"] = {"reverted": list()} for domain in domains: result = {} try: - result = __salt__['virt.revert_snapshot'](domain, snapshot=snapshot, cleanup=cleanup) - result = {'domain': domain, 'current': result['reverted'], 'deleted': result['deleted']} + result = __salt__["virt.revert_snapshot"]( + domain, snapshot=snapshot, cleanup=cleanup + ) + result = { + "domain": domain, + "current": result["reverted"], + "deleted": result["deleted"], + } except CommandExecutionError as err: if len(domains) > 1: - ignored_domains.append({'domain': domain, 'issue': six.text_type(err)}) + ignored_domains.append( + {"domain": domain, "issue": six.text_type(err)} + ) if len(domains) > 1: if result: - ret['changes']['reverted'].append(result) + ret["changes"]["reverted"].append(result) else: - ret['changes'] = result + ret["changes"] = result break - ret['result'] = len(domains) != len(ignored_domains) - if ret['result']: - ret['comment'] = 'Domain{0} has been reverted'.format(len(domains) > 1 and "s" or "") + ret["result"] = len(domains) != len(ignored_domains) + if ret["result"]: + ret["comment"] = "Domain{0} has been reverted".format( + len(domains) > 1 and "s" or "" + ) if ignored_domains: - ret['changes']['ignored'] = ignored_domains - if not ret['changes']['reverted']: - ret['changes'].pop('reverted') + ret["changes"]["ignored"] = ignored_domains + if not ret["changes"]["reverted"]: + ret["changes"].pop("reverted") except libvirt.libvirtError as err: - ret['comment'] = six.text_type(err) + ret["comment"] = six.text_type(err) except CommandExecutionError as err: - ret['comment'] = six.text_type(err) + ret["comment"] = six.text_type(err) return ret -def network_defined(name, - bridge, - forward, - vport=None, - tag=None, - ipv4_config=None, - ipv6_config=None, - autostart=True, - connection=None, - username=None, - password=None): - ''' +def network_defined( + name, + bridge, + forward, + vport=None, + tag=None, + ipv4_config=None, + ipv6_config=None, + autostart=True, + connection=None, + username=None, + password=None, +): + """ Defines a new network with specified arguments. :param bridge: Bridge name @@ -899,53 +942,60 @@ def network_defined(name, end: 192.168.42.150 - autostart: True - ''' - ret = {'name': name, - 'changes': {}, - 'result': True if not __opts__['test'] else None, - 'comment': '' - } + """ + ret = { + "name": name, + "changes": {}, + "result": True if not __opts__["test"] else None, + "comment": "", + } try: - info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password) + info = __salt__["virt.network_info"]( + name, connection=connection, username=username, password=password + ) if info and info[name]: - ret['comment'] = 'Network {0} exists'.format(name) - ret['result'] = True + ret["comment"] = "Network {0} exists".format(name) + ret["result"] = True else: - if not __opts__['test']: - __salt__['virt.network_define'](name, - bridge, - forward, - vport=vport, - tag=tag, - ipv4_config=ipv4_config, - ipv6_config=ipv6_config, - autostart=autostart, - start=False, - connection=connection, - username=username, - password=password) - ret['changes'][name] = 'Network defined' - ret['comment'] = 'Network {0} defined'.format(name) + if not __opts__["test"]: + __salt__["virt.network_define"]( + name, + bridge, + forward, + vport=vport, + tag=tag, + ipv4_config=ipv4_config, + ipv6_config=ipv6_config, + autostart=autostart, + start=False, + connection=connection, + username=username, + password=password, + ) + ret["changes"][name] = "Network defined" + ret["comment"] = "Network {0} defined".format(name) except libvirt.libvirtError as err: - ret['result'] = False - ret['comment'] = err.get_error_message() + ret["result"] = False + ret["comment"] = err.get_error_message() return ret -def network_running(name, - bridge, - forward, - vport=None, - tag=None, - ipv4_config=None, - ipv6_config=None, - autostart=True, - connection=None, - username=None, - password=None): - ''' +def network_running( + name, + bridge, + forward, + vport=None, + tag=None, + ipv4_config=None, + ipv6_config=None, + autostart=True, + connection=None, + username=None, + password=None, +): + """ Defines and starts a new network with specified arguments. :param bridge: Bridge name @@ -1007,57 +1057,70 @@ def network_running(name, end: 192.168.42.150 - autostart: True - ''' - ret = network_defined(name, - bridge, - forward, - vport=vport, - tag=tag, - ipv4_config=ipv4_config, - ipv6_config=ipv6_config, - autostart=autostart, - connection=connection, - username=username, - password=password) - - defined = name in ret['changes'] and ret['changes'][name].startswith('Network defined') - - result = True if not __opts__['test'] else None - if ret['result'] is None or ret['result']: + """ + ret = network_defined( + name, + bridge, + forward, + vport=vport, + tag=tag, + ipv4_config=ipv4_config, + ipv6_config=ipv6_config, + autostart=autostart, + connection=connection, + username=username, + password=password, + ) + + defined = name in ret["changes"] and ret["changes"][name].startswith( + "Network defined" + ) + + result = True if not __opts__["test"] else None + if ret["result"] is None or ret["result"]: try: - info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password) + info = __salt__["virt.network_info"]( + name, connection=connection, username=username, password=password + ) # In the corner case where test=True and the network wasn't defined # we may not get the network in the info dict and that is normal. - if info.get(name, {}).get('active', False): - ret['comment'] = '{} and is running'.format(ret['comment']) + if info.get(name, {}).get("active", False): + ret["comment"] = "{} and is running".format(ret["comment"]) else: - if not __opts__['test']: - __salt__['virt.network_start'](name, connection=connection, username=username, password=password) - change = 'Network started' - if name in ret['changes']: - change = '{} and started'.format(ret['changes'][name]) - ret['changes'][name] = change - ret['comment'] = '{} and started'.format(ret['comment']) - ret['result'] = result + if not __opts__["test"]: + __salt__["virt.network_start"]( + name, + connection=connection, + username=username, + password=password, + ) + change = "Network started" + if name in ret["changes"]: + change = "{} and started".format(ret["changes"][name]) + ret["changes"][name] = change + ret["comment"] = "{} and started".format(ret["comment"]) + ret["result"] = result except libvirt.libvirtError as err: - ret['result'] = False - ret['comment'] = err.get_error_message() + ret["result"] = False + ret["comment"] = err.get_error_message() return ret -def pool_defined(name, - ptype=None, - target=None, - permissions=None, - source=None, - transient=False, - autostart=True, - connection=None, - username=None, - password=None): - ''' +def pool_defined( + name, + ptype=None, + target=None, + permissions=None, + source=None, + transient=False, + autostart=True, + connection=None, + username=None, + password=None, +): + """ Defines a new pool with specified arguments. .. versionadded:: sodium @@ -1097,124 +1160,150 @@ def pool_defined(name, format: cifs - autostart: True - ''' - ret = {'name': name, - 'changes': {}, - 'result': True if not __opts__['test'] else None, - 'comment': '' - } + """ + ret = { + "name": name, + "changes": {}, + "result": True if not __opts__["test"] else None, + "comment": "", + } try: - info = __salt__['virt.pool_info'](name, connection=connection, username=username, password=password) + info = __salt__["virt.pool_info"]( + name, connection=connection, username=username, password=password + ) needs_autostart = False if info: - needs_autostart = info[name]['autostart'] and not autostart or not info[name]['autostart'] and autostart + needs_autostart = ( + info[name]["autostart"] + and not autostart + or not info[name]["autostart"] + and autostart + ) # Update can happen for both running and stopped pools - needs_update = __salt__['virt.pool_update'](name, - ptype=ptype, - target=target, - permissions=permissions, - source_devices=(source or {}).get('devices'), - source_dir=(source or {}).get('dir'), - source_initiator=(source or {}).get('initiator'), - source_adapter=(source or {}).get('adapter'), - source_hosts=(source or {}).get('hosts'), - source_auth=(source or {}).get('auth'), - source_name=(source or {}).get('name'), - source_format=(source or {}).get('format'), - test=True, - connection=connection, - username=username, - password=password) + needs_update = __salt__["virt.pool_update"]( + name, + ptype=ptype, + target=target, + permissions=permissions, + source_devices=(source or {}).get("devices"), + source_dir=(source or {}).get("dir"), + source_initiator=(source or {}).get("initiator"), + source_adapter=(source or {}).get("adapter"), + source_hosts=(source or {}).get("hosts"), + source_auth=(source or {}).get("auth"), + source_name=(source or {}).get("name"), + source_format=(source or {}).get("format"), + test=True, + connection=connection, + username=username, + password=password, + ) if needs_update: - if not __opts__['test']: - __salt__['virt.pool_update'](name, - ptype=ptype, - target=target, - permissions=permissions, - source_devices=(source or {}).get('devices'), - source_dir=(source or {}).get('dir'), - source_initiator=(source or {}).get('initiator'), - source_adapter=(source or {}).get('adapter'), - source_hosts=(source or {}).get('hosts'), - source_auth=(source or {}).get('auth'), - source_name=(source or {}).get('name'), - source_format=(source or {}).get('format'), - connection=connection, - username=username, - password=password) - - action = '' - if info[name]['state'] != 'running': - if not __opts__['test']: - __salt__['virt.pool_build'](name, connection=connection, username=username, password=password) - action = ', built' - - action = '{}, autostart flag changed'.format(action) if needs_autostart else action - ret['changes'][name] = 'Pool updated{0}'.format(action) - ret['comment'] = 'Pool {0} updated{1}'.format(name, action) + if not __opts__["test"]: + __salt__["virt.pool_update"]( + name, + ptype=ptype, + target=target, + permissions=permissions, + source_devices=(source or {}).get("devices"), + source_dir=(source or {}).get("dir"), + source_initiator=(source or {}).get("initiator"), + source_adapter=(source or {}).get("adapter"), + source_hosts=(source or {}).get("hosts"), + source_auth=(source or {}).get("auth"), + source_name=(source or {}).get("name"), + source_format=(source or {}).get("format"), + connection=connection, + username=username, + password=password, + ) + + action = "" + if info[name]["state"] != "running": + if not __opts__["test"]: + __salt__["virt.pool_build"]( + name, + connection=connection, + username=username, + password=password, + ) + action = ", built" + + action = ( + "{}, autostart flag changed".format(action) + if needs_autostart + else action + ) + ret["changes"][name] = "Pool updated{0}".format(action) + ret["comment"] = "Pool {0} updated{1}".format(name, action) else: - ret['comment'] = 'Pool {0} unchanged'.format(name) - ret['result'] = True + ret["comment"] = "Pool {0} unchanged".format(name) + ret["result"] = True else: needs_autostart = autostart - if not __opts__['test']: - __salt__['virt.pool_define'](name, - ptype=ptype, - target=target, - permissions=permissions, - source_devices=(source or {}).get('devices'), - source_dir=(source or {}).get('dir'), - source_initiator=(source or {}).get('initiator'), - source_adapter=(source or {}).get('adapter'), - source_hosts=(source or {}).get('hosts'), - source_auth=(source or {}).get('auth'), - source_name=(source or {}).get('name'), - source_format=(source or {}).get('format'), - transient=transient, - start=False, - connection=connection, - username=username, - password=password) - - __salt__['virt.pool_build'](name, - connection=connection, - username=username, - password=password) + if not __opts__["test"]: + __salt__["virt.pool_define"]( + name, + ptype=ptype, + target=target, + permissions=permissions, + source_devices=(source or {}).get("devices"), + source_dir=(source or {}).get("dir"), + source_initiator=(source or {}).get("initiator"), + source_adapter=(source or {}).get("adapter"), + source_hosts=(source or {}).get("hosts"), + source_auth=(source or {}).get("auth"), + source_name=(source or {}).get("name"), + source_format=(source or {}).get("format"), + transient=transient, + start=False, + connection=connection, + username=username, + password=password, + ) + + __salt__["virt.pool_build"]( + name, connection=connection, username=username, password=password + ) if needs_autostart: - ret['changes'][name] = 'Pool defined, marked for autostart' - ret['comment'] = 'Pool {0} defined, marked for autostart'.format(name) + ret["changes"][name] = "Pool defined, marked for autostart" + ret["comment"] = "Pool {0} defined, marked for autostart".format(name) else: - ret['changes'][name] = 'Pool defined' - ret['comment'] = 'Pool {0} defined'.format(name) + ret["changes"][name] = "Pool defined" + ret["comment"] = "Pool {0} defined".format(name) if needs_autostart: - if not __opts__['test']: - __salt__['virt.pool_set_autostart'](name, - state='on' if autostart else 'off', - connection=connection, - username=username, - password=password) + if not __opts__["test"]: + __salt__["virt.pool_set_autostart"]( + name, + state="on" if autostart else "off", + connection=connection, + username=username, + password=password, + ) except libvirt.libvirtError as err: - ret['comment'] = err.get_error_message() - ret['result'] = False + ret["comment"] = err.get_error_message() + ret["result"] = False return ret -def pool_running(name, - ptype=None, - target=None, - permissions=None, - source=None, - transient=False, - autostart=True, - connection=None, - username=None, - password=None): - ''' +def pool_running( + name, + ptype=None, + target=None, + permissions=None, + source=None, + transient=False, + autostart=True, + connection=None, + username=None, + password=None, +): + """ Defines and starts a new pool with specified arguments. .. versionadded:: 2019.2.0 @@ -1230,8 +1319,6 @@ def pool_running(name, when set to ``True``, the pool will be automatically undefined after being stopped. (Default: ``False``) :param autostart: Whether to start the pool when booting the host. (Default: ``True``) - :param start: - When ``True``, define and start the pool, otherwise the pool will be left stopped. :param connection: libvirt connection URI, overriding defaults :param username: username to connect with, overriding defaults :param password: password to connect with, overriding defaults @@ -1259,68 +1346,83 @@ def pool_running(name, format: cifs - autostart: True - ''' - ret = pool_defined(name, - ptype=ptype, - target=target, - permissions=permissions, - source=source, - transient=transient, - autostart=autostart, - connection=connection, - username=username, - password=password) - defined = name in ret['changes'] and ret['changes'][name].startswith('Pool defined') - updated = name in ret['changes'] and ret['changes'][name].startswith('Pool updated') - - result = True if not __opts__['test'] else None - if ret['result'] is None or ret['result']: + """ + ret = pool_defined( + name, + ptype=ptype, + target=target, + permissions=permissions, + source=source, + transient=transient, + autostart=autostart, + connection=connection, + username=username, + password=password, + ) + defined = name in ret["changes"] and ret["changes"][name].startswith("Pool defined") + updated = name in ret["changes"] and ret["changes"][name].startswith("Pool updated") + + result = True if not __opts__["test"] else None + if ret["result"] is None or ret["result"]: try: - info = __salt__['virt.pool_info'](name, connection=connection, username=username, password=password) - action = 'started' - # In the corner case where test=True and the pool wasn't defined + info = __salt__["virt.pool_info"]( + name, connection=connection, username=username, password=password + ) + action = "started" + # In the corner case where test=True and the pool wasn"t defined # we may get not get our pool in the info dict and that is normal. - is_running = info.get(name, {}).get('state', 'stopped') == 'running' + is_running = info.get(name, {}).get("state", "stopped") == "running" if is_running: if updated: - action = 'built, restarted' - if not __opts__['test']: - __salt__['virt.pool_stop'](name, connection=connection, username=username, password=password) - if not __opts__['test']: - __salt__['virt.pool_build'](name, connection=connection, username=username, password=password) + action = "built, restarted" + if not __opts__["test"]: + __salt__["virt.pool_stop"]( + name, + connection=connection, + username=username, + password=password, + ) + if not __opts__["test"]: + __salt__["virt.pool_build"]( + name, + connection=connection, + username=username, + password=password, + ) else: - action = 'already running' + action = "already running" result = True if not is_running or updated or defined: - if not __opts__['test']: - __salt__['virt.pool_start'](name, connection=connection, username=username, password=password) + if not __opts__["test"]: + __salt__["virt.pool_start"]( + name, + connection=connection, + username=username, + password=password, + ) - comment = 'Pool {0}'.format(name) - change = 'Pool' - if name in ret['changes']: - comment = '{0},'.format(ret['comment']) - change = '{0},'.format(ret['changes'][name]) + comment = "Pool {0}".format(name) + change = "Pool" + if name in ret["changes"]: + comment = "{0},".format(ret["comment"]) + change = "{0},".format(ret["changes"][name]) - if action != 'already running': - ret['changes'][name] = '{0} {1}'.format(change, action) + if action != "already running": + ret["changes"][name] = "{0} {1}".format(change, action) - ret['comment'] = '{0} {1}'.format(comment, action) - ret['result'] = result + ret["comment"] = "{0} {1}".format(comment, action) + ret["result"] = result except libvirt.libvirtError as err: - ret['comment'] = err.get_error_message() - ret['result'] = False + ret["comment"] = err.get_error_message() + ret["result"] = False return ret -def pool_deleted(name, - purge=False, - connection=None, - username=None, - password=None): - ''' +def pool_deleted(name, purge=False, connection=None, username=None, password=None): + """ Deletes a virtual storage pool. :param name: the name of the pool to delete. @@ -1345,81 +1447,253 @@ def pool_deleted(name, - purge: True .. versionadded:: 3000 - ''' - ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''} + """ + ret = {"name": name, "changes": {}, "result": True, "comment": ""} try: - info = __salt__['virt.pool_info'](name, connection=connection, username=username, password=password) + info = __salt__["virt.pool_info"]( + name, connection=connection, username=username, password=password + ) if info: - ret['changes']['stopped'] = False - ret['changes']['deleted'] = False - ret['changes']['undefined'] = False - ret['changes']['deleted_volumes'] = [] + ret["changes"]["stopped"] = False + ret["changes"]["deleted"] = False + ret["changes"]["undefined"] = False + ret["changes"]["deleted_volumes"] = [] unsupported = [] - if info[name]['state'] == 'running': + if info[name]["state"] == "running": if purge: - unsupported_volume_delete = ['iscsi', 'iscsi-direct', 'mpath', 'scsi'] - if info[name]['type'] not in unsupported_volume_delete: - __salt__['virt.pool_refresh'](name, - connection=connection, - username=username, - password=password) - volumes = __salt__['virt.pool_list_volumes'](name, - connection=connection, - username=username, - password=password) + unsupported_volume_delete = [ + "iscsi", + "iscsi-direct", + "mpath", + "scsi", + ] + if info[name]["type"] not in unsupported_volume_delete: + __salt__["virt.pool_refresh"]( + name, + connection=connection, + username=username, + password=password, + ) + volumes = __salt__["virt.pool_list_volumes"]( + name, + connection=connection, + username=username, + password=password, + ) for volume in volumes: # Not supported for iSCSI and SCSI drivers - deleted = __opts__['test'] - if not __opts__['test']: - deleted = __salt__['virt.volume_delete'](name, - volume, - connection=connection, - username=username, - password=password) + deleted = __opts__["test"] + if not __opts__["test"]: + deleted = __salt__["virt.volume_delete"]( + name, + volume, + connection=connection, + username=username, + password=password, + ) if deleted: - ret['changes']['deleted_volumes'].append(volume) + ret["changes"]["deleted_volumes"].append(volume) else: - unsupported.append('deleting volume') - - if not __opts__['test']: - ret['changes']['stopped'] = __salt__['virt.pool_stop'](name, - connection=connection, - username=username, - password=password) + unsupported.append("deleting volume") + + if not __opts__["test"]: + ret["changes"]["stopped"] = __salt__["virt.pool_stop"]( + name, + connection=connection, + username=username, + password=password, + ) else: - ret['changes']['stopped'] = True + ret["changes"]["stopped"] = True if purge: - supported_pool_delete = ['dir', 'fs', 'netfs', 'logical', 'vstorage', 'zfs'] - if info[name]['type'] in supported_pool_delete: - if not __opts__['test']: - ret['changes']['deleted'] = __salt__['virt.pool_delete'](name, - connection=connection, - username=username, - password=password) + supported_pool_delete = [ + "dir", + "fs", + "netfs", + "logical", + "vstorage", + "zfs", + ] + if info[name]["type"] in supported_pool_delete: + if not __opts__["test"]: + ret["changes"]["deleted"] = __salt__["virt.pool_delete"]( + name, + connection=connection, + username=username, + password=password, + ) else: - ret['changes']['deleted'] = True + ret["changes"]["deleted"] = True else: - unsupported.append('deleting pool') + unsupported.append("deleting pool") - if not __opts__['test']: - ret['changes']['undefined'] = __salt__['virt.pool_undefine'](name, - connection=connection, - username=username, - password=password) + if not __opts__["test"]: + ret["changes"]["undefined"] = __salt__["virt.pool_undefine"]( + name, connection=connection, username=username, password=password + ) else: - ret['changes']['undefined'] = True - ret['result'] = None + ret["changes"]["undefined"] = True + ret["result"] = None if unsupported: - ret['comment'] = 'Unsupported actions for pool of type "{0}": {1}'.format(info[name]['type'], - ', '.join(unsupported)) + ret[ + "comment" + ] = 'Unsupported actions for pool of type "{0}": {1}'.format( + info[name]["type"], ", ".join(unsupported) + ) else: - ret['comment'] = 'Storage pool could not be found: {0}'.format(name) + ret["comment"] = "Storage pool could not be found: {0}".format(name) except libvirt.libvirtError as err: - ret['comment'] = 'Failed deleting pool: {0}'.format(err.get_error_message()) - ret['result'] = False + ret["comment"] = "Failed deleting pool: {0}".format(err.get_error_message()) + ret["result"] = False + + return ret + + +def volume_defined( + pool, + name, + size, + allocation=0, + format=None, + type=None, + permissions=None, + backing_store=None, + nocow=False, + connection=None, + username=None, + password=None, +): + """ + Ensure a disk volume is existing. + + :param pool: name of the pool containing the volume + :param name: name of the volume + :param size: capacity of the volume to define in MiB + :param allocation: allocated size of the volume in MiB. Defaults to 0. + :param format: + volume format. The allowed values are depending on the pool type. + Check the virt.pool_capabilities output for the possible values and the default. + :param type: + type of the volume. One of file, block, dir, network, netdiri, ploop or None. + By default, the type is guessed by libvirt from the pool type. + :param permissions: + Permissions to set on the target folder. This is mostly used for filesystem-based + pool types. See :ref:`pool-define-permissions` for more details on this structure. + :param backing_store: + dictionary describing a backing file for the volume. It must contain a ``path`` + property pointing to the base volume and a ``format`` property defining the format + of the base volume. + + The base volume format will not be guessed for security reasons and is thus mandatory. + :param nocow: disable COW for the volume. + :param connection: libvirt connection URI, overriding defaults + :param username: username to connect with, overriding defaults + :param password: password to connect with, overriding defaults + + .. rubric:: CLI Example: + Volume on ESX: + + .. code-block:: yaml + + esx_volume: + virt.volume_defined: + - pool: "[local-storage]" + - name: myvm/myvm.vmdk + - size: 8192 + + QCow2 volume with backing file: + + .. code-block:: bash + + myvolume: + virt.volume_defined: + - pool: default + - name: myvm.qcow2 + - format: qcow2 + - size: 8192 + - permissions: + mode: '0775' + owner: '123' + group: '345' + - backing_store: + path: /path/to/base.img + format: raw + - nocow: True + + .. versionadded:: Sodium + """ + ret = {"name": name, "changes": {}, "result": True, "comment": ""} + + pools = __salt__["virt.list_pools"]( + connection=connection, username=username, password=password + ) + if pool not in pools: + raise SaltInvocationError("Storage pool {} not existing".format(pool)) + + vol_infos = ( + __salt__["virt.volume_infos"]( + pool, name, connection=connection, username=username, password=password + ) + .get(pool, {}) + .get(name) + ) + + if vol_infos: + ret["comment"] = "volume is existing" + # if backing store or format are different, return an error + backing_store_info = vol_infos.get("backing_store") or {} + same_backing_store = backing_store_info.get("path") == ( + backing_store or {} + ).get("path") and backing_store_info.get("format") == (backing_store or {}).get( + "format" + ) + if not same_backing_store or ( + vol_infos.get("format") != format and format is not None + ): + ret["result"] = False + ret[ + "comment" + ] = "A volume with the same name but different backing store or format is existing" + return ret + + # otherwise assume the volume has already been defined + # if the sizes don't match, issue a warning comment: too dangerous to do this for now + if int(vol_infos.get("capacity")) != int(size) * 1024 * 1024: + ret[ + "comment" + ] = "The capacity of the volume is different, but no resize performed" + return ret + + ret["result"] = None if __opts__["test"] else True + test_comment = "would be " + try: + if not __opts__["test"]: + __salt__["virt.volume_define"]( + pool, + name, + size, + allocation=allocation, + format=format, + type=type, + permissions=permissions, + backing_store=backing_store, + nocow=nocow, + connection=connection, + username=username, + password=password, + ) + test_comment = "" + + ret["comment"] = "Volume {} {}defined in pool {}".format( + name, test_comment, pool + ) + ret["changes"] = {"{}/{}".format(pool, name): {"old": "", "new": "defined"}} + except libvirt.libvirtError as err: + ret["comment"] = err.get_error_message() + ret["result"] = False return ret diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja index fdaea168f2..aac6283eb0 100644 --- a/salt/templates/virt/libvirt_domain.jinja +++ b/salt/templates/virt/libvirt_domain.jinja @@ -15,6 +15,12 @@ {% if 'cmdline' in boot %} <cmdline>{{ boot.cmdline }}</cmdline> {% endif %} + {% if 'loader' in boot %} + <loader readonly='yes' type='pflash'>{{ boot.loader }}</loader> + {% endif %} + {% if 'nvram' in boot %} + <nvram template='{{boot.nvram}}'></nvram> + {% endif %} {% endif %} {% for dev in boot_dev %} <boot dev='{{ dev }}' /> @@ -22,16 +28,31 @@ </os> <devices> {% for disk in disks %} - <disk type='file' device='{{ disk.device }}'> - {% if 'source_file' in disk %} + <disk type='{{ disk.type }}' device='{{ disk.device }}'> + {% if disk.type == 'file' and 'source_file' in disk -%} <source file='{{ disk.source_file }}' /> {% endif %} + {% if disk.type == 'volume' and 'pool' in disk -%} + <source pool='{{ disk.pool }}' volume='{{ disk.volume }}' /> + {% endif %} + {%- if disk.type == 'network' %} + <source protocol='{{ disk.protocol }}' name='{{ disk.volume }}'{% if disk.get('query') %} query='{{ disk.query }}'{% endif %}> + {%- for host in disk.get('hosts') %} + <host name='{{ host.name }}'{% if host.get("port") %} port='{{ host.port }}'{% endif %}/> + {%- endfor %} + {%- if disk.get("auth") %} + <auth username='{{ disk.auth.username }}'> + <secret type='{{ disk.auth.type }}' usage='{{ disk.auth.usage}}'/> + </auth> + {%- endif %} + </source> + {%- endif %} <target dev='{{ disk.target_dev }}' bus='{{ disk.disk_bus }}' /> {% if disk.address -%} <address type='drive' controller='0' bus='0' target='0' unit='{{ disk.index }}' /> {% endif %} {% if disk.driver -%} - <driver name='qemu' type='{{ disk.type }}' cache='none' io='native'/> + <driver name='qemu' type='{{ disk.format}}' cache='none' io='native'/> {% endif %} </disk> {% endfor %} @@ -43,7 +64,9 @@ {% for nic in nics %} <interface type='{{ nic.type }}'> <source {{ nic.type }}='{{ nic.source }}'/> + {% if nic.get('mac') -%} <mac address='{{ nic.mac }}'/> + {%- endif %} {% if nic.model %}<model type='{{ nic.model }}'/>{% endif %} </interface> {% endfor %} diff --git a/salt/templates/virt/libvirt_volume.jinja b/salt/templates/virt/libvirt_volume.jinja index 5cbe098826..8e5748179f 100644 --- a/salt/templates/virt/libvirt_volume.jinja +++ b/salt/templates/virt/libvirt_volume.jinja @@ -1,17 +1,39 @@ -<volume> - <name>{{ name }}/{{ filename }}</name> - <key>{{ name }}/{{ volname }}</key> +<volume{% if type %} type='{{ type }}'{% endif %}> + <name>{{ name }}</name> <source> </source> <capacity unit='KiB'>{{ size }}</capacity> - <allocation unit='KiB'>0</allocation> + <allocation unit='KiB'>{{ allocation }}</allocation> <target> - <path>{{ pool }}{{ name }}/{{ filename }}</path> - <format type='{{ disktype }}'/> + {%- if format %} + <format type='{{ format }}'/> + {%- endif %} + {%- if target.permissions -%} <permissions> - <mode>00</mode> - <owner>0</owner> - <group>0</group> + {%- if target.permissions.get('mode') %} + <mode>{{ target.permissions.mode }}</mode> + {%- endif %} + {%- if target.permissions.get('owner') %} + <owner>{{ target.permissions.owner }}</owner> + {%- endif %} + {%- if target.permissions.get('group') %} + <group>{{ target.permissions.group }}</group> + {%- endif %} + {%- if target.permissions.get('label') %} + <label>{{ target.permissions.label }}</label> + {%- endif %} </permissions> + {%- endif %} + {%- if target.nocow %} + <nocow/> + {%- endif %} </target> + {%- if backingStore %} + <backingStore> + <path>{{ backingStore.path }}</path> + {%- if backingStore.format %} + <format type='{{ backingStore.format }}'/> + {%- endif %} + </backingStore> + {%- endif %} </volume> diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index 3e9bd5ef49..d3988464f6 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -1,865 +1,1238 @@ # -*- coding: utf-8 -*- -''' +""" virt execution module unit tests -''' +""" # pylint: disable=3rd-party-module-not-gated # Import python libs from __future__ import absolute_import, print_function, unicode_literals -import os -import re + import datetime +import os import shutil +import tempfile -# Import Salt Testing libs -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.unit import TestCase -from tests.support.mock import MagicMock, patch +import salt.config +import salt.modules.config as config +import salt.modules.virt as virt +import salt.syspaths # Import salt libs import salt.utils.yaml -import salt.modules.virt as virt -import salt.modules.config as config from salt._compat import ElementTree as ET -import salt.config -import salt.syspaths -import tempfile -from salt.exceptions import CommandExecutionError +from salt.exceptions import CommandExecutionError, SaltInvocationError # Import third party libs from salt.ext import six + # pylint: disable=import-error from salt.ext.six.moves import range # pylint: disable=redefined-builtin +# Import Salt Testing libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.mock import MagicMock, patch +from tests.support.unit import TestCase + # pylint: disable=invalid-name,protected-access,attribute-defined-outside-init,too-many-public-methods,unused-argument class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors - ''' + """ Libvirt library mock - ''' + """ + class virDomain(MagicMock): - ''' + """ virDomain mock - ''' + """ class libvirtError(Exception): - ''' + """ libvirtError mock - ''' + """ + + def __init__(self, msg): + super().__init__(msg) + self.msg = msg + + def get_error_message(self): + return self.msg class VirtTestCase(TestCase, LoaderModuleMockMixin): - ''' + """ Test cases for salt.module.virt - ''' + """ def setup_loader_modules(self): self.mock_libvirt = LibvirtMock() self.mock_conn = MagicMock() + self.mock_conn.getStoragePoolCapabilities.return_value = ( + "<storagepoolCapabilities/>" + ) self.mock_libvirt.openAuth.return_value = self.mock_conn self.mock_popen = MagicMock() - self.addCleanup(delattr, self, 'mock_libvirt') - self.addCleanup(delattr, self, 'mock_conn') - self.addCleanup(delattr, self, 'mock_popen') + self.addCleanup(delattr, self, "mock_libvirt") + self.addCleanup(delattr, self, "mock_conn") + self.addCleanup(delattr, self, "mock_popen") self.mock_subprocess = MagicMock() - self.mock_subprocess.return_value = self.mock_subprocess # pylint: disable=no-member - self.mock_subprocess.Popen.return_value = self.mock_popen # pylint: disable=no-member + self.mock_subprocess.return_value = ( + self.mock_subprocess + ) # pylint: disable=no-member + self.mock_subprocess.Popen.return_value = ( + self.mock_popen + ) # pylint: disable=no-member loader_globals = { - '__salt__': { - 'config.get': config.get, - 'config.option': config.option, - }, - 'libvirt': self.mock_libvirt, - 'subprocess': self.mock_subprocess + "__salt__": {"config.get": config.get, "config.option": config.option}, + "libvirt": self.mock_libvirt, + "subprocess": self.mock_subprocess, } return {virt: loader_globals, config: loader_globals} def set_mock_vm(self, name, xml): - ''' + """ Define VM to use in tests - ''' - self.mock_conn.listDefinedDomains.return_value = [name] # pylint: disable=no-member + """ + self.mock_conn.listDefinedDomains.return_value = [ + name + ] # pylint: disable=no-member mock_domain = self.mock_libvirt.virDomain() - self.mock_conn.lookupByName.return_value = mock_domain # pylint: disable=no-member + self.mock_conn.lookupByName.return_value = ( + mock_domain # pylint: disable=no-member + ) mock_domain.XMLDesc.return_value = xml # pylint: disable=no-member # Return state as shutdown - mock_domain.info.return_value = [4, 2048 * 1024, 1024 * 1024, 2, 1234] # pylint: disable=no-member + mock_domain.info.return_value = [ + 4, + 2048 * 1024, + 1024 * 1024, + 2, + 1234, + ] # pylint: disable=no-member mock_domain.ID.return_value = 1 mock_domain.name.return_value = name return mock_domain def test_disk_profile_merge(self): - ''' + """ Test virt._disk_profile() when merging with user-defined disks - ''' - root_dir = os.path.join(salt.syspaths.ROOT_DIR, 'srv', 'salt-images') - userdisks = [{'name': 'data', 'size': 16384, 'format': 'raw'}] - - disks = virt._disk_profile('default', 'kvm', userdisks, 'myvm', image='/path/to/image') - self.assertEqual( - [{'name': 'system', - 'device': 'disk', - 'size': 8192, - 'format': 'qcow2', - 'model': 'virtio', - 'filename': 'myvm_system.qcow2', - 'image': '/path/to/image', - 'source_file': '{0}{1}myvm_system.qcow2'.format(root_dir, os.sep)}, - {'name': 'data', - 'device': 'disk', - 'size': 16384, - 'format': 'raw', - 'model': 'virtio', - 'filename': 'myvm_data.raw', - 'source_file': '{0}{1}myvm_data.raw'.format(root_dir, os.sep)}], - disks + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + userdisks = [ + {"name": "system", "image": "/path/to/image"}, + {"name": "data", "size": 16384, "format": "raw"}, + ] + + disks = virt._disk_profile(self.mock_conn, "default", "kvm", userdisks, "myvm") + self.assertEqual( + [ + { + "name": "system", + "device": "disk", + "size": 8192, + "format": "qcow2", + "model": "virtio", + "filename": "myvm_system.qcow2", + "image": "/path/to/image", + "source_file": "{0}{1}myvm_system.qcow2".format(root_dir, os.sep), + }, + { + "name": "data", + "device": "disk", + "size": 16384, + "format": "raw", + "model": "virtio", + "filename": "myvm_data.raw", + "source_file": "{0}{1}myvm_data.raw".format(root_dir, os.sep), + }, + ], + disks, ) def test_boot_default_dev(self): - ''' + """ Test virt._gen_xml() default boot device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64' - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64" + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('os/boot').attrib['dev'], 'hd') - self.assertEqual(root.find('os/type').attrib['arch'], 'x86_64') - self.assertEqual(root.find('os/type').text, 'hvm') + self.assertEqual(root.find("os/boot").attrib["dev"], "hd") + self.assertEqual(root.find("os/type").attrib["arch"], "x86_64") + self.assertEqual(root.find("os/type").text, "hvm") def test_boot_custom_dev(self): - ''' + """ Test virt._gen_xml() custom boot device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - boot_dev='cdrom' - ) + "kvm", + "hvm", + "x86_64", + boot_dev="cdrom", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('os/boot').attrib['dev'], 'cdrom') + self.assertEqual(root.find("os/boot").attrib["dev"], "cdrom") def test_boot_multiple_devs(self): - ''' + """ Test virt._gen_xml() multiple boot devices - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - boot_dev='cdrom network' - ) + "kvm", + "hvm", + "x86_64", + boot_dev="cdrom network", + ) root = ET.fromstring(xml_data) - devs = root.findall('.//boot') + devs = root.findall(".//boot") self.assertTrue(len(devs) == 2) def test_gen_xml_no_nic(self): - ''' + """ Test virt._gen_xml() serial console - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='pty', - console=True - ) + "kvm", + "hvm", + "x86_64", + serial_type="pty", + console=True, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'pty') - self.assertEqual(root.find('devices/console').attrib['type'], 'pty') + self.assertEqual(root.find("devices/serial").attrib["type"], "pty") + self.assertEqual(root.find("devices/console").attrib["type"], "pty") def test_gen_xml_for_serial_console(self): - ''' + """ Test virt._gen_xml() serial console - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='pty', - console=True - ) + "kvm", + "hvm", + "x86_64", + serial_type="pty", + console=True, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'pty') - self.assertEqual(root.find('devices/console').attrib['type'], 'pty') + self.assertEqual(root.find("devices/serial").attrib["type"], "pty") + self.assertEqual(root.find("devices/console").attrib["type"], "pty") def test_gen_xml_for_telnet_console(self): - ''' + """ Test virt._gen_xml() telnet console - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='tcp', + "kvm", + "hvm", + "x86_64", + serial_type="tcp", console=True, - telnet_port=22223 - ) + telnet_port=22223, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'tcp') - self.assertEqual(root.find('devices/console').attrib['type'], 'tcp') - self.assertEqual(root.find('devices/console/source').attrib['service'], '22223') + self.assertEqual(root.find("devices/serial").attrib["type"], "tcp") + self.assertEqual(root.find("devices/console").attrib["type"], "tcp") + self.assertEqual(root.find("devices/console/source").attrib["service"], "22223") def test_gen_xml_for_telnet_console_unspecified_port(self): - ''' + """ Test virt._gen_xml() telnet console without any specified port - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='tcp', - console=True - ) + "kvm", + "hvm", + "x86_64", + serial_type="tcp", + console=True, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'tcp') - self.assertEqual(root.find('devices/console').attrib['type'], 'tcp') - self.assertIsInstance(int(root.find('devices/console/source').attrib['service']), int) + self.assertEqual(root.find("devices/serial").attrib["type"], "tcp") + self.assertEqual(root.find("devices/console").attrib["type"], "tcp") + self.assertIsInstance( + int(root.find("devices/console/source").attrib["service"]), int + ) def test_gen_xml_for_serial_no_console(self): - ''' + """ Test virt._gen_xml() with no serial console - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='pty', - console=False - ) + "kvm", + "hvm", + "x86_64", + serial_type="pty", + console=False, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'pty') - self.assertEqual(root.find('devices/console'), None) + self.assertEqual(root.find("devices/serial").attrib["type"], "pty") + self.assertEqual(root.find("devices/console"), None) def test_gen_xml_for_telnet_no_console(self): - ''' + """ Test virt._gen_xml() with no telnet console - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - serial_type='tcp', + "kvm", + "hvm", + "x86_64", + serial_type="tcp", console=False, - ) + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/serial').attrib['type'], 'tcp') - self.assertEqual(root.find('devices/console'), None) + self.assertEqual(root.find("devices/serial").attrib["type"], "tcp") + self.assertEqual(root.find("devices/console"), None) def test_gen_xml_nographics_default(self): - ''' + """ Test virt._gen_xml() with default no graphics device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64' - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64" + ) + root = ET.fromstring(xml_data) + self.assertIsNone(root.find("devices/graphics")) + + def test_gen_xml_noloader_default(self): + """ + Test virt._gen_xml() with default no loader + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") + xml_data = virt._gen_xml( + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64" + ) root = ET.fromstring(xml_data) - self.assertIsNone(root.find('devices/graphics')) + self.assertIsNone(root.find("os/loader")) def test_gen_xml_vnc_default(self): - ''' + """ Test virt._gen_xml() with default vnc graphics device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - graphics={'type': 'vnc', 'port': 1234, 'tlsPort': 5678, - 'listen': {'type': 'address', 'address': 'myhost'}}, - ) + "kvm", + "hvm", + "x86_64", + graphics={ + "type": "vnc", + "port": 1234, + "tlsPort": 5678, + "listen": {"type": "address", "address": "myhost"}, + }, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/graphics').attrib['type'], 'vnc') - self.assertEqual(root.find('devices/graphics').attrib['autoport'], 'no') - self.assertEqual(root.find('devices/graphics').attrib['port'], '1234') - self.assertFalse('tlsPort' in root.find('devices/graphics').attrib) - self.assertEqual(root.find('devices/graphics').attrib['listen'], 'myhost') - self.assertEqual(root.find('devices/graphics/listen').attrib['type'], 'address') - self.assertEqual(root.find('devices/graphics/listen').attrib['address'], 'myhost') + self.assertEqual(root.find("devices/graphics").attrib["type"], "vnc") + self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no") + self.assertEqual(root.find("devices/graphics").attrib["port"], "1234") + self.assertFalse("tlsPort" in root.find("devices/graphics").attrib) + self.assertEqual(root.find("devices/graphics").attrib["listen"], "myhost") + self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address") + self.assertEqual( + root.find("devices/graphics/listen").attrib["address"], "myhost" + ) def test_gen_xml_spice_default(self): - ''' + """ Test virt._gen_xml() with default spice graphics device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - graphics={'type': 'spice'}, - ) + "kvm", + "hvm", + "x86_64", + graphics={"type": "spice"}, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/graphics').attrib['type'], 'spice') - self.assertEqual(root.find('devices/graphics').attrib['autoport'], 'yes') - self.assertEqual(root.find('devices/graphics').attrib['listen'], '0.0.0.0') - self.assertEqual(root.find('devices/graphics/listen').attrib['type'], 'address') - self.assertEqual(root.find('devices/graphics/listen').attrib['address'], '0.0.0.0') + self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") + self.assertEqual(root.find("devices/graphics").attrib["autoport"], "yes") + self.assertEqual(root.find("devices/graphics").attrib["listen"], "0.0.0.0") + self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address") + self.assertEqual( + root.find("devices/graphics/listen").attrib["address"], "0.0.0.0" + ) def test_gen_xml_spice(self): - ''' + """ Test virt._gen_xml() with spice graphics device - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'kvm', - 'hvm', - 'x86_64', - graphics={'type': 'spice', 'port': 1234, 'tls_port': 5678, 'listen': {'type': 'none'}}, - ) + "kvm", + "hvm", + "x86_64", + graphics={ + "type": "spice", + "port": 1234, + "tls_port": 5678, + "listen": {"type": "none"}, + }, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('devices/graphics').attrib['type'], 'spice') - self.assertEqual(root.find('devices/graphics').attrib['autoport'], 'no') - self.assertEqual(root.find('devices/graphics').attrib['port'], '1234') - self.assertEqual(root.find('devices/graphics').attrib['tlsPort'], '5678') - self.assertFalse('listen' in root.find('devices/graphics').attrib) - self.assertEqual(root.find('devices/graphics/listen').attrib['type'], 'none') - self.assertFalse('address' in root.find('devices/graphics/listen').attrib) + self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") + self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no") + self.assertEqual(root.find("devices/graphics").attrib["port"], "1234") + self.assertEqual(root.find("devices/graphics").attrib["tlsPort"], "5678") + self.assertFalse("listen" in root.find("devices/graphics").attrib) + self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "none") + self.assertFalse("address" in root.find("devices/graphics/listen").attrib) def test_default_disk_profile_hypervisor_esxi(self): - ''' + """ Test virt._disk_profile() default ESXi profile - ''' + """ mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._disk_profile('nonexistent', 'vmware') + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._disk_profile( + self.mock_conn, "nonexistent", "vmware", None, "test-vm" + ) self.assertTrue(len(ret) == 1) - found = [disk for disk in ret if disk['name'] == 'system'] + found = [disk for disk in ret if disk["name"] == "system"] self.assertTrue(bool(found)) system = found[0] - self.assertEqual(system['format'], 'vmdk') - self.assertEqual(system['model'], 'scsi') - self.assertTrue(int(system['size']) >= 1) + self.assertEqual(system["format"], "vmdk") + self.assertEqual(system["model"], "scsi") + self.assertTrue(int(system["size"]) >= 1) def test_default_disk_profile_hypervisor_kvm(self): - ''' + """ Test virt._disk_profile() default KVM profile - ''' - mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._disk_profile('nonexistent', 'kvm') + """ + mock = MagicMock(side_effect=[{}, "/images/dir"]) + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._disk_profile( + self.mock_conn, "nonexistent", "kvm", None, "test-vm" + ) self.assertTrue(len(ret) == 1) - found = [disk for disk in ret if disk['name'] == 'system'] + found = [disk for disk in ret if disk["name"] == "system"] self.assertTrue(bool(found)) system = found[0] - self.assertEqual(system['format'], 'qcow2') - self.assertEqual(system['model'], 'virtio') - self.assertTrue(int(system['size']) >= 1) + self.assertEqual(system["format"], "qcow2") + self.assertEqual(system["model"], "virtio") + self.assertTrue(int(system["size"]) >= 1) def test_default_disk_profile_hypervisor_xen(self): - ''' + """ Test virt._disk_profile() default XEN profile - ''' - mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._disk_profile('nonexistent', 'xen') + """ + mock = MagicMock(side_effect=[{}, "/images/dir"]) + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._disk_profile( + self.mock_conn, "nonexistent", "xen", None, "test-vm" + ) self.assertTrue(len(ret) == 1) - found = [disk for disk in ret if disk['name'] == 'system'] + found = [disk for disk in ret if disk["name"] == "system"] self.assertTrue(bool(found)) system = found[0] - self.assertEqual(system['format'], 'qcow2') - self.assertEqual(system['model'], 'xen') - self.assertTrue(int(system['size']) >= 1) + self.assertEqual(system["format"], "qcow2") + self.assertEqual(system["model"], "xen") + self.assertTrue(int(system["size"]) >= 1) def test_default_nic_profile_hypervisor_esxi(self): - ''' + """ Test virt._nic_profile() default ESXi profile - ''' + """ mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._nic_profile('nonexistent', 'vmware') + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._nic_profile("nonexistent", "vmware") self.assertTrue(len(ret) == 1) eth0 = ret[0] - self.assertEqual(eth0['name'], 'eth0') - self.assertEqual(eth0['type'], 'bridge') - self.assertEqual(eth0['source'], 'DEFAULT') - self.assertEqual(eth0['model'], 'e1000') + self.assertEqual(eth0["name"], "eth0") + self.assertEqual(eth0["type"], "bridge") + self.assertEqual(eth0["source"], "DEFAULT") + self.assertEqual(eth0["model"], "e1000") def test_default_nic_profile_hypervisor_kvm(self): - ''' + """ Test virt._nic_profile() default KVM profile - ''' + """ mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._nic_profile('nonexistent', 'kvm') + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._nic_profile("nonexistent", "kvm") self.assertTrue(len(ret) == 1) eth0 = ret[0] - self.assertEqual(eth0['name'], 'eth0') - self.assertEqual(eth0['type'], 'bridge') - self.assertEqual(eth0['source'], 'br0') - self.assertEqual(eth0['model'], 'virtio') + self.assertEqual(eth0["name"], "eth0") + self.assertEqual(eth0["type"], "bridge") + self.assertEqual(eth0["source"], "br0") + self.assertEqual(eth0["model"], "virtio") def test_default_nic_profile_hypervisor_xen(self): - ''' + """ Test virt._nic_profile() default XEN profile - ''' + """ mock = MagicMock(return_value={}) - with patch.dict(virt.__salt__, {'config.get': mock}): # pylint: disable=no-member - ret = virt._nic_profile('nonexistent', 'xen') + with patch.dict( + virt.__salt__, {"config.get": mock} # pylint: disable=no-member + ): + ret = virt._nic_profile("nonexistent", "xen") self.assertTrue(len(ret) == 1) eth0 = ret[0] - self.assertEqual(eth0['name'], 'eth0') - self.assertEqual(eth0['type'], 'bridge') - self.assertEqual(eth0['source'], 'br0') - self.assertFalse(eth0['model']) - - def test_gen_vol_xml(self): - ''' - Test virt._get_vol_xml() - ''' - xml_data = virt._gen_vol_xml('vmname', 'system', 'qcow2', 8192, '/path/to/image/') + self.assertEqual(eth0["name"], "eth0") + self.assertEqual(eth0["type"], "bridge") + self.assertEqual(eth0["source"], "br0") + self.assertFalse(eth0["model"]) + + def test_gen_vol_xml_esx(self): + """ + Test virt._get_vol_xml() for the ESX case + """ + xml_data = virt._gen_vol_xml("vmname/system.vmdk", 8192, format="vmdk") + root = ET.fromstring(xml_data) + self.assertIsNone(root.get("type")) + self.assertEqual(root.find("name").text, "vmname/system.vmdk") + self.assertEqual(root.find("capacity").attrib["unit"], "KiB") + self.assertEqual(root.find("capacity").text, six.text_type(8192 * 1024)) + self.assertEqual(root.find("allocation").text, six.text_type(0)) + self.assertEqual(root.find("target/format").get("type"), "vmdk") + self.assertIsNone(root.find("target/permissions")) + self.assertIsNone(root.find("target/nocow")) + self.assertIsNone(root.find("backingStore")) + + def test_gen_vol_xml_file(self): + """ + Test virt._get_vol_xml() for a file volume + """ + xml_data = virt._gen_vol_xml( + "myvm_system.qcow2", + 8192, + format="qcow2", + allocation=4096, + type="file", + permissions={ + "mode": "0775", + "owner": "123", + "group": "456", + "label": "sec_label", + }, + backing_store={"path": "/backing/image", "format": "raw"}, + nocow=True, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'vmname/system.qcow2') - self.assertEqual(root.find('key').text, 'vmname/system') - self.assertEqual(root.find('capacity').attrib['unit'], 'KiB') - self.assertEqual(root.find('capacity').text, six.text_type(8192 * 1024)) + self.assertEqual(root.get("type"), "file") + self.assertEqual(root.find("name").text, "myvm_system.qcow2") + self.assertIsNone(root.find("key")) + self.assertIsNone(root.find("target/path")) + self.assertEqual(root.find("target/format").get("type"), "qcow2") + self.assertEqual(root.find("capacity").attrib["unit"], "KiB") + self.assertEqual(root.find("capacity").text, six.text_type(8192 * 1024)) + self.assertEqual(root.find("capacity").attrib["unit"], "KiB") + self.assertEqual(root.find("allocation").text, six.text_type(4096 * 1024)) + self.assertEqual(root.find("target/permissions/mode").text, "0775") + self.assertEqual(root.find("target/permissions/owner").text, "123") + self.assertEqual(root.find("target/permissions/group").text, "456") + self.assertEqual(root.find("target/permissions/label").text, "sec_label") + self.assertIsNotNone(root.find("target/nocow")) + self.assertEqual(root.find("backingStore/path").text, "/backing/image") + self.assertEqual(root.find("backingStore/format").get("type"), "raw") def test_gen_xml_for_kvm_default_profile(self): - ''' + """ Test virt._gen_xml(), KVM default profile case - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.attrib['type'], 'kvm') - self.assertEqual(root.find('vcpu').text, '1') - self.assertEqual(root.find('memory').text, six.text_type(512 * 1024)) - self.assertEqual(root.find('memory').attrib['unit'], 'KiB') + self.assertEqual(root.attrib["type"], "kvm") + self.assertEqual(root.find("vcpu").text, "1") + self.assertEqual(root.find("memory").text, six.text_type(512 * 1024)) + self.assertEqual(root.find("memory").attrib["unit"], "KiB") - disks = root.findall('.//disk') + disks = root.findall(".//disk") self.assertEqual(len(disks), 1) disk = disks[0] - root_dir = salt.config.DEFAULT_MINION_OPTS.get('root_dir') - self.assertTrue(disk.find('source').attrib['file'].startswith(root_dir)) - self.assertTrue('hello_system' in disk.find('source').attrib['file']) - self.assertEqual(disk.find('target').attrib['dev'], 'vda') - self.assertEqual(disk.find('target').attrib['bus'], 'virtio') - self.assertEqual(disk.find('driver').attrib['name'], 'qemu') - self.assertEqual(disk.find('driver').attrib['type'], 'qcow2') - - interfaces = root.findall('.//interface') + root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir") + self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir)) + self.assertTrue("hello_system" in disk.find("source").attrib["file"]) + self.assertEqual(disk.find("target").attrib["dev"], "vda") + self.assertEqual(disk.find("target").attrib["bus"], "virtio") + self.assertEqual(disk.find("driver").attrib["name"], "qemu") + self.assertEqual(disk.find("driver").attrib["type"], "qcow2") + + interfaces = root.findall(".//interface") self.assertEqual(len(interfaces), 1) iface = interfaces[0] - self.assertEqual(iface.attrib['type'], 'bridge') - self.assertEqual(iface.find('source').attrib['bridge'], 'br0') - self.assertEqual(iface.find('model').attrib['type'], 'virtio') - - mac = iface.find('mac').attrib['address'] - self.assertTrue( - re.match('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$', mac, re.I)) + self.assertEqual(iface.attrib["type"], "bridge") + self.assertEqual(iface.find("source").attrib["bridge"], "br0") + self.assertEqual(iface.find("model").attrib["type"], "virtio") def test_gen_xml_for_esxi_default_profile(self): - ''' + """ Test virt._gen_xml(), ESXi/vmware default profile case - ''' - diskp = virt._disk_profile('default', 'vmware', [], 'hello') - nicp = virt._nic_profile('default', 'vmware') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello") + nicp = virt._nic_profile("default", "vmware") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'vmware', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.attrib['type'], 'vmware') - self.assertEqual(root.find('vcpu').text, '1') - self.assertEqual(root.find('memory').text, six.text_type(512 * 1024)) - self.assertEqual(root.find('memory').attrib['unit'], 'KiB') + self.assertEqual(root.attrib["type"], "vmware") + self.assertEqual(root.find("vcpu").text, "1") + self.assertEqual(root.find("memory").text, six.text_type(512 * 1024)) + self.assertEqual(root.find("memory").attrib["unit"], "KiB") - disks = root.findall('.//disk') + disks = root.findall(".//disk") self.assertEqual(len(disks), 1) disk = disks[0] - self.assertTrue('[0]' in disk.find('source').attrib['file']) - self.assertTrue('hello_system' in disk.find('source').attrib['file']) - self.assertEqual(disk.find('target').attrib['dev'], 'sda') - self.assertEqual(disk.find('target').attrib['bus'], 'scsi') - self.assertEqual(disk.find('address').attrib['unit'], '0') + self.assertTrue("[0]" in disk.find("source").attrib["file"]) + self.assertTrue("hello_system" in disk.find("source").attrib["file"]) + self.assertEqual(disk.find("target").attrib["dev"], "sda") + self.assertEqual(disk.find("target").attrib["bus"], "scsi") + self.assertEqual(disk.find("address").attrib["unit"], "0") - interfaces = root.findall('.//interface') + interfaces = root.findall(".//interface") self.assertEqual(len(interfaces), 1) iface = interfaces[0] - self.assertEqual(iface.attrib['type'], 'bridge') - self.assertEqual(iface.find('source').attrib['bridge'], 'DEFAULT') - self.assertEqual(iface.find('model').attrib['type'], 'e1000') - - mac = iface.find('mac').attrib['address'] - self.assertTrue( - re.match('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$', mac, re.I)) + self.assertEqual(iface.attrib["type"], "bridge") + self.assertEqual(iface.find("source").attrib["bridge"], "DEFAULT") + self.assertEqual(iface.find("model").attrib["type"], "e1000") def test_gen_xml_for_xen_default_profile(self): - ''' + """ Test virt._gen_xml(), XEN PV default profile case - ''' - diskp = virt._disk_profile('default', 'xen', [], 'hello') - nicp = virt._nic_profile('default', 'xen') - with patch.dict(virt.__grains__, {'os_family': 'Suse'}): + """ + diskp = virt._disk_profile(self.mock_conn, "default", "xen", [], "hello") + nicp = virt._nic_profile("default", "xen") + with patch.dict( + virt.__grains__, {"os_family": "Suse"} # pylint: disable=no-member + ): xml_data = virt._gen_xml( - 'hello', + self.mock_conn, + "hello", 1, 512, diskp, nicp, - 'xen', - 'xen', - 'x86_64', - boot=None - ) + "xen", + "xen", + "x86_64", + boot=None, + ) root = ET.fromstring(xml_data) - self.assertEqual(root.attrib['type'], 'xen') - self.assertEqual(root.find('vcpu').text, '1') - self.assertEqual(root.find('memory').text, six.text_type(512 * 1024)) - self.assertEqual(root.find('memory').attrib['unit'], 'KiB') - self.assertEqual(root.find('.//kernel').text, '/usr/lib/grub2/x86_64-xen/grub.xen') + self.assertEqual(root.attrib["type"], "xen") + self.assertEqual(root.find("vcpu").text, "1") + self.assertEqual(root.find("memory").text, six.text_type(512 * 1024)) + self.assertEqual(root.find("memory").attrib["unit"], "KiB") + self.assertEqual( + root.find(".//kernel").text, "/usr/lib/grub2/x86_64-xen/grub.xen" + ) - disks = root.findall('.//disk') + disks = root.findall(".//disk") self.assertEqual(len(disks), 1) disk = disks[0] - root_dir = salt.config.DEFAULT_MINION_OPTS.get('root_dir') - self.assertTrue(disk.find('source').attrib['file'].startswith(root_dir)) - self.assertTrue('hello_system' in disk.find('source').attrib['file']) - self.assertEqual(disk.find('target').attrib['dev'], 'xvda') - self.assertEqual(disk.find('target').attrib['bus'], 'xen') - self.assertEqual(disk.find('driver').attrib['name'], 'qemu') - self.assertEqual(disk.find('driver').attrib['type'], 'qcow2') - - interfaces = root.findall('.//interface') + root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir") + self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir)) + self.assertTrue("hello_system" in disk.find("source").attrib["file"]) + self.assertEqual(disk.find("target").attrib["dev"], "xvda") + self.assertEqual(disk.find("target").attrib["bus"], "xen") + self.assertEqual(disk.find("driver").attrib["name"], "qemu") + self.assertEqual(disk.find("driver").attrib["type"], "qcow2") + + interfaces = root.findall(".//interface") self.assertEqual(len(interfaces), 1) iface = interfaces[0] - self.assertEqual(iface.attrib['type'], 'bridge') - self.assertEqual(iface.find('source').attrib['bridge'], 'br0') - self.assertIsNone(iface.find('model')) + self.assertEqual(iface.attrib["type"], "bridge") + self.assertEqual(iface.find("source").attrib["bridge"], "br0") + self.assertIsNone(iface.find("model")) def test_gen_xml_for_esxi_custom_profile(self): - ''' + """ Test virt._gen_xml(), ESXi/vmware custom profile case - ''' + """ disks = { - 'noeffect': [ - {'first': {'size': 8192, 'pool': 'datastore1'}}, - {'second': {'size': 4096, 'pool': 'datastore2'}} + "noeffect": [ + {"first": {"size": 8192, "pool": "datastore1"}}, + {"second": {"size": 4096, "pool": "datastore2"}}, ] } nics = { - 'noeffect': [ - {'name': 'eth1', 'source': 'ONENET'}, - {'name': 'eth2', 'source': 'TWONET'} + "noeffect": [ + {"name": "eth1", "source": "ONENET"}, + {"name": "eth2", "source": "TWONET"}, ] } - with patch.dict(virt.__salt__, # pylint: disable=no-member - {'config.get': MagicMock(side_effect=[disks, nics])}): - diskp = virt._disk_profile('noeffect', 'vmware', [], 'hello') - nicp = virt._nic_profile('noeffect', 'vmware') + with patch.dict( + virt.__salt__, # pylint: disable=no-member + {"config.get": MagicMock(side_effect=[disks, nics])}, + ): + diskp = virt._disk_profile( + self.mock_conn, "noeffect", "vmware", [], "hello" + ) + nicp = virt._nic_profile("noeffect", "vmware") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'vmware', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.attrib['type'], 'vmware') - self.assertEqual(root.find('vcpu').text, '1') - self.assertEqual(root.find('memory').text, six.text_type(512 * 1024)) - self.assertEqual(root.find('memory').attrib['unit'], 'KiB') - self.assertTrue(len(root.findall('.//disk')) == 2) - self.assertTrue(len(root.findall('.//interface')) == 2) + self.assertEqual(root.attrib["type"], "vmware") + self.assertEqual(root.find("vcpu").text, "1") + self.assertEqual(root.find("memory").text, six.text_type(512 * 1024)) + self.assertEqual(root.find("memory").attrib["unit"], "KiB") + self.assertTrue(len(root.findall(".//disk")) == 2) + self.assertTrue(len(root.findall(".//interface")) == 2) def test_gen_xml_for_kvm_custom_profile(self): - ''' + """ Test virt._gen_xml(), KVM custom profile case - ''' + """ disks = { - 'noeffect': [ - {'first': {'size': 8192, 'pool': '/var/lib/images'}}, - {'second': {'size': 4096, 'pool': '/var/lib/images'}} + "noeffect": [ + {"first": {"size": 8192, "pool": "/var/lib/images"}}, + {"second": {"size": 4096, "pool": "/var/lib/images"}}, ] } nics = { - 'noeffect': [ - {'name': 'eth1', 'source': 'b2'}, - {'name': 'eth2', 'source': 'b2'} + "noeffect": [ + {"name": "eth1", "source": "b2"}, + {"name": "eth2", "source": "b2"}, ] } - with patch.dict(virt.__salt__, {'config.get': MagicMock(side_effect=[ # pylint: disable=no-member - disks, nics])}): - diskp = virt._disk_profile('noeffect', 'kvm', [], 'hello') - nicp = virt._nic_profile('noeffect', 'kvm') + with patch.dict( + virt.__salt__, # pylint: disable=no-member + {"config.get": MagicMock(side_effect=[disks, nics])}, + ): + diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello") + nicp = virt._nic_profile("noeffect", "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.attrib['type'], 'kvm') - self.assertEqual(root.find('vcpu').text, '1') - self.assertEqual(root.find('memory').text, six.text_type(512 * 1024)) - self.assertEqual(root.find('memory').attrib['unit'], 'KiB') - self.assertTrue(len(root.findall('.//disk')) == 2) - self.assertTrue(len(root.findall('.//interface')) == 2) - - @patch('salt.modules.virt.pool_info', - return_value={'mypool': {'target_path': os.path.join(salt.syspaths.ROOT_DIR, 'pools', 'mypool')}}) - def test_disk_profile_kvm_disk_pool(self, mock_poolinfo): - ''' - Test virt._gen_xml(), KVM case with pools defined. - ''' + self.assertEqual(root.attrib["type"], "kvm") + self.assertEqual(root.find("vcpu").text, "1") + self.assertEqual(root.find("memory").text, six.text_type(512 * 1024)) + self.assertEqual(root.find("memory").attrib["unit"], "KiB") + disks = root.findall(".//disk") + self.assertTrue(len(disks) == 2) + self.assertEqual(disks[0].find("target").get("dev"), "vda") + self.assertEqual(disks[1].find("target").get("dev"), "vdb") + self.assertTrue(len(root.findall(".//interface")) == 2) + + def test_disk_profile_kvm_disk_pool(self): + """ + Test virt._disk_profile(), KVM case with pools defined. + """ disks = { - 'noeffect': [ - {'first': {'size': 8192, 'pool': 'mypool'}}, - {'second': {'size': 4096}} + "noeffect": [ + {"first": {"size": 8192, "pool": "mypool"}}, + {"second": {"size": 4096}}, ] } # pylint: disable=no-member - with patch.dict(virt.__salt__, {'config.get': MagicMock(side_effect=[ - disks, - os.path.join(salt.syspaths.ROOT_DIR, 'default', 'path')])}): + with patch.dict( + virt.__salt__, + { + "config.get": MagicMock( + side_effect=[ + disks, + os.path.join(salt.syspaths.ROOT_DIR, "default", "path"), + ] + ) + }, + ): - diskp = virt._disk_profile('noeffect', 'kvm', [], 'hello') + diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello") - pools_path = os.path.join(salt.syspaths.ROOT_DIR, 'pools', 'mypool') + os.sep - default_path = os.path.join(salt.syspaths.ROOT_DIR, 'default', 'path') + os.sep + pools_path = ( + os.path.join(salt.syspaths.ROOT_DIR, "pools", "mypool") + os.sep + ) + default_path = ( + os.path.join(salt.syspaths.ROOT_DIR, "default", "path") + os.sep + ) self.assertEqual(len(diskp), 2) - self.assertTrue(diskp[0]['source_file'].startswith(pools_path)) - self.assertTrue(diskp[1]['source_file'].startswith(default_path)) + self.assertTrue(diskp[1]["source_file"].startswith(default_path)) # pylint: enable=no-member def test_disk_profile_kvm_disk_external_image(self): - ''' + """ Test virt._gen_xml(), KVM case with an external image. - ''' - diskp = virt._disk_profile(None, 'kvm', [ + """ + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}): + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "mydisk", "source_file": "/path/to/my/image.qcow2"}], + "hello", + ) + + self.assertEqual(len(diskp), 1) + self.assertEqual(diskp[0]["source_file"], ("/path/to/my/image.qcow2")) + + def test_disk_profile_cdrom_default(self): + """ + Test virt._gen_xml(), KVM case with cdrom. + """ + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}): + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [ + { + "name": "mydisk", + "device": "cdrom", + "source_file": "/path/to/my.iso", + } + ], + "hello", + ) + + self.assertEqual(len(diskp), 1) + self.assertEqual(diskp[0]["model"], "ide") + self.assertEqual(diskp[0]["format"], "raw") + + def test_disk_profile_pool_disk_type(self): + """ + Test virt._disk_profile(), with a disk pool of disk type + """ + self.mock_conn.listStoragePools.return_value = ["test-vdb"] + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """ + <pool type="disk"> + <name>test-vdb</name> + <source> + <device path='/dev/vdb'/> + </source> + <target> + <path>/dev</path> + </target> + </pool> + """ + + # No existing disk case + self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = ( + [] + ) + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "mydisk", "pool": "test-vdb"}], + "hello", + ) + self.assertEqual(diskp[0]["filename"], ("vdb1")) + + # Append to the end case + self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [ + "vdb1", + "vdb2", + ] + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "mydisk", "pool": "test-vdb"}], + "hello", + ) + self.assertEqual(diskp[0]["filename"], ("vdb3")) + + # Hole in the middle case + self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [ + "vdb1", + "vdb3", + ] + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "mydisk", "pool": "test-vdb"}], + "hello", + ) + self.assertEqual(diskp[0]["filename"], ("vdb2")) + + # Reuse existing volume case + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "mydisk", "pool": "test-vdb", "source_file": "vdb1"}], + "hello", + ) + self.assertEqual(diskp[0]["filename"], ("vdb1")) + + def test_gen_xml_volume(self): + """ + Test virt._gen_xml(), generating a disk of volume type + """ + self.mock_conn.listStoragePools.return_value = ["default"] + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = ( + "<pool type='dir'/>" + ) + self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [ + "myvolume" + ] + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [ + {"name": "system", "pool": "default"}, + {"name": "data", "pool": "default", "source_file": "myvolume"}, + ], + "hello", + ) + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = ( + "<pool type='dir'/>" + ) + nicp = virt._nic_profile(None, "kvm") + xml_data = virt._gen_xml( + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) + root = ET.fromstring(xml_data) + disk = root.findall(".//disk")[0] + self.assertEqual(disk.attrib["device"], "disk") + self.assertEqual(disk.attrib["type"], "volume") + source = disk.find("source") + self.assertEqual("default", source.attrib["pool"]) + self.assertEqual("hello_system", source.attrib["volume"]) + self.assertEqual("myvolume", root.find(".//disk[2]/source").get("volume")) + + # RBD volume usage auth test case + self.mock_conn.listStoragePools.return_value = ["test-rbd"] + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """ + <pool type='rbd'> + <name>test-rbd</name> + <uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid> + <capacity unit='bytes'>526133493760</capacity> + <allocation unit='bytes'>589928</allocation> + <available unit='bytes'>515081306112</available> + <source> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret usage='pool_test-rbd'/> + </auth> + </source> + </pool> + """ + self.mock_conn.getStoragePoolCapabilities.return_value = """ + <storagepoolCapabilities> + <pool type='rbd' supported='yes'> + <volOptions> + <defaultFormat type='raw'/> + <enum name='targetFormatType'> + </enum> + </volOptions> + </pool> + </storagepoolCapabilities> + """ + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "system", "pool": "test-rbd"}], + "test-vm", + ) + xml_data = virt._gen_xml( + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) + root = ET.fromstring(xml_data) + disk = root.findall(".//disk")[0] + self.assertDictEqual( { - 'name': 'mydisk', - 'source_file': '/path/to/my/image.qcow2' - }], 'hello') - - self.assertEqual(len(diskp), 1) - self.assertEqual(diskp[0]['source_file'], ('/path/to/my/image.qcow2')) - - @patch('salt.modules.virt.pool_info', return_value={}) - def test_disk_profile_kvm_disk_pool_notfound(self, mock_poolinfo): - ''' - Test virt._gen_xml(), KVM case with pools defined. - ''' - disks = { - 'noeffect': [ - {'first': {'size': 8192, 'pool': 'default'}}, - ] - } - with patch.dict(virt.__salt__, {'config.get': MagicMock(side_effect=[ # pylint: disable=no-member - disks, "/default/path/"])}): - with self.assertRaises(CommandExecutionError): - virt._disk_profile('noeffect', 'kvm', [], 'hello') - - @patch('salt.modules.virt.pool_info', return_value={'target_path': '/dev/disk/by-path'}) - def test_disk_profile_kvm_disk_pool_invalid(self, mock_poolinfo): - ''' - Test virt._gen_xml(), KVM case with pools defined. - ''' - disks = { - 'noeffect': [ - {'first': {'size': 8192, 'pool': 'default'}}, - ] - } - with patch.dict(virt.__salt__, {'config.get': MagicMock(side_effect=[ # pylint: disable=no-member - disks, "/default/path/"])}): - with self.assertRaises(CommandExecutionError): - virt._disk_profile('noeffect', 'kvm', [], 'hello') + "type": "network", + "device": "disk", + "source": { + "protocol": "rbd", + "name": "libvirt-pool/test-vm_system", + "host": [ + {"name": "ses2.tf.local"}, + {"name": "ses3.tf.local", "port": "1234"}, + ], + "auth": { + "username": "libvirt", + "secret": {"type": "ceph", "usage": "pool_test-rbd"}, + }, + }, + "target": {"dev": "vda", "bus": "virtio"}, + "driver": { + "name": "qemu", + "type": "raw", + "cache": "none", + "io": "native", + }, + }, + salt.utils.xmlutil.to_dict(disk, True), + ) + + # RBD volume UUID auth test case + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """ + <pool type='rbd'> + <name>test-rbd</name> + <uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid> + <capacity unit='bytes'>526133493760</capacity> + <allocation unit='bytes'>589928</allocation> + <available unit='bytes'>515081306112</available> + <source> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret uuid='some-uuid'/> + </auth> + </source> + </pool> + """ + self.mock_conn.secretLookupByUUIDString.return_value.usageID.return_value = ( + "pool_test-rbd" + ) + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "system", "pool": "test-rbd"}], + "test-vm", + ) + xml_data = virt._gen_xml( + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) + root = ET.fromstring(xml_data) + self.assertDictEqual( + { + "username": "libvirt", + "secret": {"type": "ceph", "usage": "pool_test-rbd"}, + }, + salt.utils.xmlutil.to_dict(root.find(".//disk/source/auth"), True), + ) + self.mock_conn.secretLookupByUUIDString.assert_called_once_with("some-uuid") + + # Disk volume test case + self.mock_conn.getStoragePoolCapabilities.return_value = """ + <storagepoolCapabilities> + <pool type='disk' supported='yes'> + <volOptions> + <defaultFormat type='none'/> + <enum name='targetFormatType'> + <value>none</value> + <value>linux</value> + <value>fat16</value> + </enum> + </volOptions> + </pool> + </storagepoolCapabilities> + """ + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """ + <pool type='disk'> + <name>test-vdb</name> + <source> + <device path='/dev/vdb'/> + <format type='gpt'/> + </source> + </pool> + """ + self.mock_conn.listStoragePools.return_value = ["test-vdb"] + self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [ + "vdb1", + ] + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [{"name": "system", "pool": "test-vdb"}], + "test-vm", + ) + xml_data = virt._gen_xml( + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) + root = ET.fromstring(xml_data) + disk = root.findall(".//disk")[0] + self.assertEqual(disk.attrib["type"], "volume") + source = disk.find("source") + self.assertEqual("test-vdb", source.attrib["pool"]) + self.assertEqual("vdb2", source.attrib["volume"]) + self.assertEqual("raw", disk.find("driver").get("type")) def test_gen_xml_cdrom(self): - ''' + """ Test virt._gen_xml(), generating a cdrom device (different disk type, no source) - ''' - diskp = virt._disk_profile(None, 'kvm', [{ - 'name': 'tested', - 'device': 'cdrom', - 'source_file': None, - 'model': 'ide'}], 'hello') - nicp = virt._nic_profile(None, 'kvm') + """ + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = ( + "<pool type='dir'/>" + ) + diskp = virt._disk_profile( + self.mock_conn, + None, + "kvm", + [ + {"name": "system", "pool": "default"}, + { + "name": "tested", + "device": "cdrom", + "source_file": None, + "model": "ide", + }, + { + "name": "remote", + "device": "cdrom", + "source_file": "http://myhost:8080/url/to/image?query=foo&filter=bar", + "model": "ide", + }, + ], + "hello", + ) + nicp = virt._nic_profile(None, "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - disk = root.findall('.//disk')[0] - self.assertEqual(disk.attrib['device'], 'cdrom') - self.assertIsNone(disk.find('source')) + disk = root.findall(".//disk")[1] + self.assertEqual(disk.get("type"), "file") + self.assertEqual(disk.attrib["device"], "cdrom") + self.assertIsNone(disk.find("source")) + self.assertEqual(disk.find("target").get("dev"), "hda") + + disk = root.findall(".//disk")[2] + self.assertEqual(disk.get("type"), "network") + self.assertEqual(disk.attrib["device"], "cdrom") + self.assertEqual( + { + "protocol": "http", + "name": "/url/to/image", + "query": "query=foo&filter=bar", + "host": {"name": "myhost", "port": "8080"}, + }, + salt.utils.xmlutil.to_dict(disk.find("source"), True), + ) def test_controller_for_esxi(self): - ''' + """ Test virt._gen_xml() generated device controller for ESXi/vmware - ''' - diskp = virt._disk_profile('default', 'vmware', [], 'hello') - nicp = virt._nic_profile('default', 'vmware') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello") + nicp = virt._nic_profile("default", "vmware") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'vmware', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - controllers = root.findall('.//devices/controller') + controllers = root.findall(".//devices/controller") self.assertTrue(len(controllers) == 1) controller = controllers[0] - self.assertEqual(controller.attrib['model'], 'lsilogic') + self.assertEqual(controller.attrib["model"], "lsilogic") def test_controller_for_kvm(self): - ''' + """ Test virt._gen_xml() generated device controller for KVM - ''' - diskp = virt._disk_profile('default', 'kvm', [], 'hello') - nicp = virt._nic_profile('default', 'kvm') + """ + diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") + nicp = virt._nic_profile("default", "kvm") xml_data = virt._gen_xml( - 'hello', - 1, - 512, - diskp, - nicp, - 'kvm', - 'hvm', - 'x86_64', - ) + self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64", + ) root = ET.fromstring(xml_data) - controllers = root.findall('.//devices/controller') + controllers = root.findall(".//devices/controller") # There should be no controller self.assertTrue(len(controllers) == 0) - # kvm mac address shoud start with 52:54:00 - self.assertTrue("mac address='52:54:00" in xml_data) def test_diff_disks(self): - ''' + """ Test virt._diff_disks() - ''' - old_disks = ET.fromstring(''' + """ + old_disks = ET.fromstring( + """ <devices> <disk type='file' device='disk'> <source file='/path/to/img0.qcow2'/> @@ -881,9 +1254,11 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <target dev='hdc' bus='ide'/> </disk> </devices> - ''').findall('disk') + """ + ).findall("disk") - new_disks = ET.fromstring(''' + new_disks = ET.fromstring( + """ <devices> <disk type='file' device='disk'> <source file='/path/to/img3.qcow2'/> @@ -901,35 +1276,63 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <target dev='hda' bus='ide'/> </disk> </devices> - ''').findall('disk') + """ + ).findall("disk") ret = virt._diff_disk_lists(old_disks, new_disks) - self.assertEqual([disk.find('source').get('file') if disk.find('source') is not None else None - for disk in ret['unchanged']], []) - self.assertEqual([disk.find('source').get('file') if disk.find('source') is not None else None - for disk in ret['new']], - ['/path/to/img3.qcow2', '/path/to/img0.qcow2', '/path/to/img4.qcow2', None]) - self.assertEqual([disk.find('target').get('dev') for disk in ret['sorted']], - ['vda', 'vdb', 'vdc', 'hda']) - self.assertEqual([disk.find('source').get('file') if disk.find('source') is not None else None - for disk in ret['sorted']], - ['/path/to/img3.qcow2', - '/path/to/img0.qcow2', - '/path/to/img4.qcow2', - None]) - self.assertEqual(ret['new'][1].find('target').get('bus'), 'virtio') - self.assertEqual([disk.find('source').get('file') if disk.find('source') is not None else None - for disk in ret['deleted']], - ['/path/to/img0.qcow2', - '/path/to/img1.qcow2', - '/path/to/img2.qcow2', - '/path/to/img4.qcow2', - None]) + self.assertEqual( + [ + disk.find("source").get("file") + if disk.find("source") is not None + else None + for disk in ret["unchanged"] + ], + [], + ) + self.assertEqual( + [ + disk.find("source").get("file") + if disk.find("source") is not None + else None + for disk in ret["new"] + ], + ["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None], + ) + self.assertEqual( + [disk.find("target").get("dev") for disk in ret["sorted"]], + ["vda", "vdb", "vdc", "hda"], + ) + self.assertEqual( + [ + disk.find("source").get("file") + if disk.find("source") is not None + else None + for disk in ret["sorted"] + ], + ["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None], + ) + self.assertEqual(ret["new"][1].find("target").get("bus"), "virtio") + self.assertEqual( + [ + disk.find("source").get("file") + if disk.find("source") is not None + else None + for disk in ret["deleted"] + ], + [ + "/path/to/img0.qcow2", + "/path/to/img1.qcow2", + "/path/to/img2.qcow2", + "/path/to/img4.qcow2", + None, + ], + ) def test_diff_nics(self): - ''' + """ Test virt._diff_nics() - ''' - old_nics = ET.fromstring(''' + """ + old_nics = ET.fromstring( + """ <devices> <interface type='network'> <mac address='52:54:00:39:02:b1'/> @@ -950,9 +1353,11 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> </interface> </devices> - ''').findall('interface') + """ + ).findall("interface") - new_nics = ET.fromstring(''' + new_nics = ET.fromstring( + """ <devices> <interface type='network'> <mac address='52:54:00:39:02:b1'/> @@ -970,20 +1375,27 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <model type='virtio'/> </interface> </devices> - ''').findall('interface') + """ + ).findall("interface") ret = virt._diff_interface_lists(old_nics, new_nics) - self.assertEqual([nic.find('mac').get('address') for nic in ret['unchanged']], - ['52:54:00:39:02:b1']) - self.assertEqual([nic.find('mac').get('address') for nic in ret['new']], - ['52:54:00:39:02:b2', '52:54:00:39:02:b4']) - self.assertEqual([nic.find('mac').get('address') for nic in ret['deleted']], - ['52:54:00:39:02:b2', '52:54:00:39:02:b3']) + self.assertEqual( + [nic.find("mac").get("address") for nic in ret["unchanged"]], + ["52:54:00:39:02:b1"], + ) + self.assertEqual( + [nic.find("mac").get("address") for nic in ret["new"]], + ["52:54:00:39:02:b2", "52:54:00:39:02:b4"], + ) + self.assertEqual( + [nic.find("mac").get("address") for nic in ret["deleted"]], + ["52:54:00:39:02:b2", "52:54:00:39:02:b3"], + ) def test_init(self): - ''' + """ Test init() function - ''' - xml = ''' + """ + xml = """ <capabilities> <host> <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid> @@ -1101,29 +1513,29 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </guest> </capabilities> - ''' + """ self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member - root_dir = os.path.join(salt.syspaths.ROOT_DIR, 'srv', 'salt-images') + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") defineMock = MagicMock(return_value=1) self.mock_conn.defineXML = defineMock mock_chmod = MagicMock() mock_run = MagicMock() - with patch.dict(os.__dict__, {'chmod': mock_chmod, 'makedirs': MagicMock()}): # pylint: disable=no-member - with patch.dict(virt.__salt__, {'cmd.run': mock_run}): # pylint: disable=no-member + with patch.dict( + os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()} + ): # pylint: disable=no-member + with patch.dict( + virt.__salt__, {"cmd.run": mock_run} + ): # pylint: disable=no-member # Ensure the init() function allows creating VM without NIC and disk - virt.init('test vm', - 2, - 1234, - nic=None, - disk=None, - seed=False, - start=False) + virt.init( + "test vm", 2, 1234, nic=None, disk=None, seed=False, start=False + ) definition = defineMock.call_args_list[0][0][0] - self.assertFalse('<interface' in definition) - self.assertFalse('<disk' in definition) + self.assertFalse("<interface" in definition) + self.assertFalse("<disk" in definition) # Ensure the init() function allows creating VM without NIC and # disk but with boot parameters. @@ -1131,56 +1543,60 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): defineMock.reset_mock() mock_run.reset_mock() boot = { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': - 'console=ttyS0 ks=http://example.com/f8-i386/os/' + "kernel": "/root/f8-i386-vmlinuz", + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", } - retval = virt.init('test vm boot params', - 2, - 1234, - nic=None, - disk=None, - seed=False, - start=False, - boot=boot) + retval = virt.init( + "test vm boot params", + 2, + 1234, + nic=None, + disk=None, + seed=False, + start=False, + boot=boot, + ) definition = defineMock.call_args_list[0][0][0] - self.assertEqual('<kernel' in definition, True) - self.assertEqual('<initrd' in definition, True) - self.assertEqual('<cmdline' in definition, True) + self.assertEqual("<kernel" in definition, True) + self.assertEqual("<initrd" in definition, True) + self.assertEqual("<cmdline" in definition, True) self.assertEqual(retval, True) # Verify that remote paths are downloaded and the xml has been # modified mock_response = MagicMock() - mock_response.read = MagicMock(return_value='filecontent') + mock_response.read = MagicMock(return_value="filecontent") cache_dir = tempfile.mkdtemp() - with patch.dict(virt.__dict__, {'CACHE_DIR': cache_dir}): - with patch('salt.ext.six.moves.urllib.request.urlopen', - MagicMock(return_value=mock_response)): - with patch('salt.utils.files.fopen', - return_value=mock_response): + with patch.dict(virt.__dict__, {"CACHE_DIR": cache_dir}): + with patch( + "salt.ext.six.moves.urllib.request.urlopen", + MagicMock(return_value=mock_response), + ): + with patch( + "salt.utils.files.fopen", return_value=mock_response + ): defineMock.reset_mock() mock_run.reset_mock() boot = { - 'kernel': - 'https://www.example.com/download/vmlinuz', - 'initrd': '', - 'cmdline': - 'console=ttyS0 ' - 'ks=http://example.com/f8-i386/os/' + "kernel": "https://www.example.com/download/vmlinuz", + "initrd": "", + "cmdline": "console=ttyS0 " + "ks=http://example.com/f8-i386/os/", } - retval = virt.init('test remote vm boot params', - 2, - 1234, - nic=None, - disk=None, - seed=False, - start=False, - boot=boot) + retval = virt.init( + "test remote vm boot params", + 2, + 1234, + nic=None, + disk=None, + seed=False, + start=False, + boot=boot, + ) definition = defineMock.call_args_list[0][0][0] self.assertEqual(cache_dir in definition, True) @@ -1189,34 +1605,125 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # Test case creating disks defineMock.reset_mock() mock_run.reset_mock() - virt.init('test vm', - 2, - 1234, - nic=None, - disk=None, - disks=[ - {'name': 'system', 'size': 10240}, - {'name': 'cddrive', 'device': 'cdrom', 'source_file': None, 'model': 'ide'} - ], - seed=False, - start=False) + pool_mock = MagicMock() + pool_mock.XMLDesc.return_value = '<pool type="dir"/>' + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + virt.init( + "test vm", + 2, + 1234, + nic=None, + disk=None, + disks=[ + {"name": "system", "size": 10240}, + { + "name": "cddrive", + "device": "cdrom", + "source_file": None, + "model": "ide", + }, + ], + seed=False, + start=False, + ) definition = ET.fromstring(defineMock.call_args_list[0][0][0]) - disk_sources = [disk.find('source').get('file') if disk.find('source') is not None else None - for disk in definition.findall('./devices/disk')] - expected_disk_path = os.path.join(root_dir, 'test vm_system.qcow2') - self.assertEqual(disk_sources, [expected_disk_path, None]) - self.assertEqual(mock_run.call_args[0][0], - 'qemu-img create -f qcow2 "{0}" 10240M'.format(expected_disk_path)) + expected_disk_path = os.path.join(root_dir, "test vm_system.qcow2") + self.assertEqual( + expected_disk_path, + definition.find("./devices/disk[1]/source").get("file"), + ) + self.assertIsNone(definition.find("./devices/disk[2]/source")) + self.assertEqual( + mock_run.call_args[0][0], + 'qemu-img create -f qcow2 "{0}" 10240M'.format(expected_disk_path), + ) self.assertEqual(mock_chmod.call_args[0][0], expected_disk_path) + # Test case creating disks volumes + defineMock.reset_mock() + mock_run.reset_mock() + vol_mock = MagicMock() + pool_mock.storageVolLookupByName.return_value = vol_mock + pool_mock.listVolumes.return_value = ["test vm_data"] + stream_mock = MagicMock() + self.mock_conn.newStream.return_value = stream_mock + self.mock_conn.listStoragePools.return_value = ["default", "test"] + with patch.dict( + os.__dict__, {"open": MagicMock(), "close": MagicMock()} + ): + cache_mock = MagicMock() + with patch.dict(virt.__salt__, {"cp.cache_file": cache_mock}): + virt.init( + "test vm", + 2, + 1234, + nic=None, + disk=None, + disks=[ + { + "name": "system", + "size": 10240, + "image": "/path/to/image", + "pool": "test", + }, + {"name": "data", "size": 10240, "pool": "default"}, + { + "name": "test", + "size": 1024, + "pool": "default", + "format": "qcow2", + "backing_store_path": "/backing/path", + "backing_store_format": "raw", + }, + ], + seed=False, + start=False, + ) + definition = ET.fromstring(defineMock.call_args_list[0][0][0]) + self.assertTrue( + all( + [ + disk.get("type") == "volume" + for disk in definition.findall("./devices/disk") + ] + ) + ) + self.assertEqual( + ["test", "default", "default"], + [ + src.get("pool") + for src in definition.findall("./devices/disk/source") + ], + ) + self.assertEqual( + ["test vm_system", "test vm_data", "test vm_test"], + [ + src.get("volume") + for src in definition.findall("./devices/disk/source") + ], + ) + + create_calls = pool_mock.createXML.call_args_list + vol_names = [ + ET.fromstring(call[0][0]).find("name").text + for call in create_calls + ] + self.assertEqual( + ["test vm_system", "test vm_test"], vol_names, + ) + + stream_mock.sendAll.assert_called_once() + stream_mock.finish.assert_called_once() + vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0) + def test_update(self): - ''' + """ Test virt.update() - ''' - root_dir = os.path.join(salt.syspaths.ROOT_DIR, 'srv', 'salt-images') - xml = ''' + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + xml = """ <domain type='kvm' id='7'> - <name>my vm</name> + <name>my_vm</name> <memory unit='KiB'>1048576</memory> <currentMemory unit='KiB'>1048576</currentMemory> <vcpu placement='auto'>1</vcpu> @@ -1226,20 +1733,33 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <devices> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> - <source file='{0}{1}my vm_system.qcow2'/> + <source file='{0}{1}my_vm_system.qcow2'/> <backingStore/> <target dev='vda' bus='virtio'/> <alias name='virtio-disk0'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> </disk> - <disk type='file' device='disk'> + <disk type='volume' device='disk'> <driver name='qemu' type='qcow2'/> - <source file='{0}{1}my vm_data.qcow2'/> + <source pool='default' volume='my_vm_data'/> <backingStore/> <target dev='vdb' bus='virtio'/> <alias name='virtio-disk1'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/> </disk> + <disk type="network" device="disk"> + <driver name='raw' type='qcow2'/> + <source protocol='rbd' name='libvirt-pool/my_vm_data2'> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <auth username='libvirt'> + <secret type='ceph' usage='pool_test-rbd'/> + </auth> + </source> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/> + </disk> <interface type='network'> <mac address='52:54:00:39:02:b1'/> <source network='default' bridge='virbr0'/> @@ -1266,78 +1786,142 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </video> </devices> </domain> - '''.format(root_dir, os.sep) - domain_mock = self.set_mock_vm('my vm', xml) - domain_mock.OSType = MagicMock(return_value='hvm') + """.format( + root_dir, os.sep + ) + domain_mock = self.set_mock_vm("my_vm", xml) + domain_mock.OSType = MagicMock(return_value="hvm") define_mock = MagicMock(return_value=True) self.mock_conn.defineXML = define_mock # No parameter passed case - self.assertEqual({ - 'definition': False, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm')) + self.assertEqual( + { + "definition": False, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm"), + ) # Same parameters passed than in default virt.defined state case - self.assertEqual({ - 'definition': False, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', - cpu=None, - mem=None, - disk_profile=None, - disks=None, - nic_profile=None, - interfaces=None, - graphics=None, - live=True, - connection=None, - username=None, - password=None, - boot=None)) + self.assertEqual( + { + "definition": False, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update( + "my_vm", + cpu=None, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + ), + ) # Update vcpus case setvcpus_mock = MagicMock(return_value=0) domain_mock.setVcpusFlags = setvcpus_mock - self.assertEqual({ - 'definition': True, - 'cpu': True, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', cpu=2)) + self.assertEqual( + { + "definition": True, + "cpu": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", cpu=2), + ) setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find('vcpu').text, '2') + self.assertEqual(setxml.find("vcpu").text, "2") self.assertEqual(setvcpus_mock.call_args[0][0], 2) boot = { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': - 'console=ttyS0 ks=http://example.com/f8-i386/os/' + "kernel": "/root/f8-i386-vmlinuz", + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", + } + + boot_uefi = { + "loader": "/usr/share/OVMF/OVMF_CODE.fd", + "nvram": "/usr/share/OVMF/OVMF_VARS.ms.fd", + } + + invalid_boot = { + "loader": "/usr/share/OVMF/OVMF_CODE.fd", + "initrd": "/root/f8-i386-initrd", } # Update with boot parameter case - self.assertEqual({ - 'definition': True, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', boot=boot)) + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", boot=boot), + ) + setxml = ET.fromstring(define_mock.call_args[0][0]) + self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz") + self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd") + self.assertEqual( + setxml.find("os").find("cmdline").text, + "console=ttyS0 ks=http://example.com/f8-i386/os/", + ) + setxml = ET.fromstring(define_mock.call_args[0][0]) + self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz") + self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd") + self.assertEqual( + setxml.find("os").find("cmdline").text, + "console=ttyS0 ks=http://example.com/f8-i386/os/", + ) + + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", boot=boot_uefi), + ) + setxml = ET.fromstring(define_mock.call_args[0][0]) + self.assertEqual( + setxml.find("os").find("loader").text, "/usr/share/OVMF/OVMF_CODE.fd" + ) + self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes") + self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash") + self.assertEqual( + setxml.find("os").find("nvram").attrib["template"], + "/usr/share/OVMF/OVMF_VARS.ms.fd", + ) + + with self.assertRaises(SaltInvocationError): + virt.update("my_vm", boot=invalid_boot) # Update memory case setmem_mock = MagicMock(return_value=0) domain_mock.setMemoryFlags = setmem_mock - self.assertEqual({ - 'definition': True, - 'mem': True, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', mem=2048)) + self.assertEqual( + { + "definition": True, + "mem": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", mem=2048), + ) setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find('memory').text, '2048') - self.assertEqual(setxml.find('memory').get('unit'), 'MiB') + self.assertEqual(setxml.find("memory").text, "2048") + self.assertEqual(setxml.find("memory").get("unit"), "MiB") self.assertEqual(setmem_mock.call_args[0][0], 2048 * 1024) # Update disks case @@ -1347,122 +1931,630 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): domain_mock.detachDevice = devdetach_mock mock_chmod = MagicMock() mock_run = MagicMock() - with patch.dict(os.__dict__, {'chmod': mock_chmod, 'makedirs': MagicMock()}): # pylint: disable=no-member - with patch.dict(virt.__salt__, {'cmd.run': mock_run}): # pylint: disable=no-member - ret = virt.update('my vm', disk_profile='default', disks=[ - {'name': 'cddrive', 'device': 'cdrom', 'source_file': None, 'model': 'ide'}, - {'name': 'added', 'size': 2048}]) + with patch.dict( + os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()} + ): # pylint: disable=no-member + with patch.dict( + virt.__salt__, {"cmd.run": mock_run} + ): # pylint: disable=no-member + ret = virt.update( + "my_vm", + disk_profile="default", + disks=[ + { + "name": "cddrive", + "device": "cdrom", + "source_file": None, + "model": "ide", + }, + {"name": "added", "size": 2048}, + ], + ) added_disk_path = os.path.join( - virt.__salt__['config.get']('virt:images'), 'my vm_added.qcow2') # pylint: disable=no-member - self.assertEqual(mock_run.call_args[0][0], - 'qemu-img create -f qcow2 "{0}" 2048M'.format(added_disk_path)) + virt.__salt__["config.get"]("virt:images"), "my_vm_added.qcow2" + ) # pylint: disable=no-member + self.assertEqual( + mock_run.call_args[0][0], + 'qemu-img create -f qcow2 "{0}" 2048M'.format(added_disk_path), + ) self.assertEqual(mock_chmod.call_args[0][0], added_disk_path) self.assertListEqual( - [None, os.path.join(root_dir, 'my vm_added.qcow2')], - [ET.fromstring(disk).find('source').get('file') if str(disk).find('<source') > -1 else None - for disk in ret['disk']['attached']]) + [None, os.path.join(root_dir, "my_vm_added.qcow2")], + [ + ET.fromstring(disk).find("source").get("file") + if str(disk).find("<source") > -1 + else None + for disk in ret["disk"]["attached"] + ], + ) self.assertListEqual( - [os.path.join(root_dir, 'my vm_data.qcow2')], - [ET.fromstring(disk).find('source').get('file') for disk in ret['disk']['detached']]) + ["my_vm_data", "libvirt-pool/my_vm_data2"], + [ + ET.fromstring(disk).find("source").get("volume") + or ET.fromstring(disk).find("source").get("name") + for disk in ret["disk"]["detached"] + ], + ) self.assertEqual(devattach_mock.call_count, 2) - devdetach_mock.assert_called_once() + self.assertEqual(devdetach_mock.call_count, 2) # Update nics case - yaml_config = ''' + yaml_config = """ virt: nic: myprofile: - network: default name: eth0 - ''' + """ mock_config = salt.utils.yaml.safe_load(yaml_config) devattach_mock.reset_mock() devdetach_mock.reset_mock() - with patch.dict(salt.modules.config.__opts__, mock_config): # pylint: disable=no-member - ret = virt.update('my vm', nic_profile='myprofile', - interfaces=[{'name': 'eth0', 'type': 'network', 'source': 'default', - 'mac': '52:54:00:39:02:b1'}, - {'name': 'eth1', 'type': 'network', 'source': 'newnet'}]) - self.assertEqual(['newnet'], - [ET.fromstring(nic).find('source').get('network') for nic in ret['interface']['attached']]) - self.assertEqual(['oldnet'], - [ET.fromstring(nic).find('source').get('network') for nic in ret['interface']['detached']]) + with patch.dict( + salt.modules.config.__opts__, mock_config # pylint: disable=no-member + ): + ret = virt.update( + "my_vm", + nic_profile="myprofile", + interfaces=[ + { + "name": "eth0", + "type": "network", + "source": "default", + "mac": "52:54:00:39:02:b1", + }, + {"name": "eth1", "type": "network", "source": "newnet"}, + ], + ) + self.assertEqual( + ["newnet"], + [ + ET.fromstring(nic).find("source").get("network") + for nic in ret["interface"]["attached"] + ], + ) + self.assertEqual( + ["oldnet"], + [ + ET.fromstring(nic).find("source").get("network") + for nic in ret["interface"]["detached"] + ], + ) devattach_mock.assert_called_once() devdetach_mock.assert_called_once() # Remove nics case devattach_mock.reset_mock() devdetach_mock.reset_mock() - ret = virt.update('my vm', nic_profile=None, interfaces=[]) - self.assertEqual([], ret['interface']['attached']) - self.assertEqual(2, len(ret['interface']['detached'])) + ret = virt.update("my_vm", nic_profile=None, interfaces=[]) + self.assertEqual([], ret["interface"]["attached"]) + self.assertEqual(2, len(ret["interface"]["detached"])) devattach_mock.assert_not_called() devdetach_mock.assert_called() # Remove disks case (yeah, it surely is silly) devattach_mock.reset_mock() devdetach_mock.reset_mock() - ret = virt.update('my vm', disk_profile=None, disks=[]) - self.assertEqual([], ret['disk']['attached']) - self.assertEqual(2, len(ret['disk']['detached'])) + ret = virt.update("my_vm", disk_profile=None, disks=[]) + self.assertEqual([], ret["disk"]["attached"]) + self.assertEqual(3, len(ret["disk"]["detached"])) devattach_mock.assert_not_called() devdetach_mock.assert_called() # Graphics change test case - self.assertEqual({ - 'definition': True, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', graphics={'type': 'vnc'})) + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", graphics={"type": "vnc"}), + ) setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual('vnc', setxml.find('devices/graphics').get('type')) + self.assertEqual("vnc", setxml.find("devices/graphics").get("type")) # Update with no diff case - self.assertEqual({ - 'definition': False, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', cpu=1, mem=1024, - disk_profile='default', disks=[{'name': 'data', 'size': 2048}], - nic_profile='myprofile', - interfaces=[{'name': 'eth0', 'type': 'network', 'source': 'default', - 'mac': '52:54:00:39:02:b1'}, - {'name': 'eth1', 'type': 'network', 'source': 'oldnet', - 'mac': '52:54:00:39:02:b2'}], - graphics={'type': 'spice', - 'listen': {'type': 'address', 'address': '127.0.0.1'}})) + pool_mock = MagicMock() + default_pool_desc = "<pool type='dir'></pool>" + rbd_pool_desc = """ + <pool type='rbd'> + <name>test-rbd</name> + <source> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret usage='pool_test-rbd'/> + </auth> + </source> + </pool> + """ + pool_mock.XMLDesc.side_effect = [ + default_pool_desc, + rbd_pool_desc, + default_pool_desc, + rbd_pool_desc, + ] + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + self.mock_conn.listStoragePools.return_value = ["test-rbd", "default"] + self.assertEqual( + { + "definition": False, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update( + "my_vm", + cpu=1, + mem=1024, + disk_profile="default", + disks=[ + {"name": "data", "size": 2048, "pool": "default"}, + { + "name": "data2", + "size": 4096, + "pool": "test-rbd", + "format": "raw", + }, + ], + nic_profile="myprofile", + interfaces=[ + { + "name": "eth0", + "type": "network", + "source": "default", + "mac": "52:54:00:39:02:b1", + }, + {"name": "eth1", "type": "network", "source": "oldnet"}, + ], + graphics={ + "type": "spice", + "listen": {"type": "address", "address": "127.0.0.1"}, + }, + ), + ) # Failed XML description update case - self.mock_conn.defineXML.side_effect = self.mock_libvirt.libvirtError("Test error") + self.mock_conn.defineXML.side_effect = self.mock_libvirt.libvirtError( + "Test error" + ) setmem_mock.reset_mock() with self.assertRaises(self.mock_libvirt.libvirtError): - virt.update('my vm', mem=2048) + virt.update("my_vm", mem=2048) # Failed single update failure case self.mock_conn.defineXML = MagicMock(return_value=True) - setmem_mock.side_effect = self.mock_libvirt.libvirtError("Failed to live change memory") - self.assertEqual({ - 'definition': True, - 'errors': ['Failed to live change memory'], - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', mem=2048)) + setmem_mock.side_effect = self.mock_libvirt.libvirtError( + "Failed to live change memory" + ) + self.assertEqual( + { + "definition": True, + "errors": ["Failed to live change memory"], + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", mem=2048), + ) # Failed multiple updates failure case - self.assertEqual({ - 'definition': True, - 'errors': ['Failed to live change memory'], - 'cpu': True, - 'disk': {'attached': [], 'detached': []}, - 'interface': {'attached': [], 'detached': []} - }, virt.update('my vm', cpu=4, mem=2048)) + self.assertEqual( + { + "definition": True, + "errors": ["Failed to live change memory"], + "cpu": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("my_vm", cpu=4, mem=2048), + ) + + def test_update_backing_store(self): + """ + Test updating a disk with a backing store + """ + xml = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2' cache='none' io='native'/> + <source pool='default' volume='my_vm_system' index='1'/> + <backingStore type='file' index='2'> + <format type='qcow2'/> + <source file='/path/to/base.qcow2'/> + <backingStore/> + </backingStore> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + </devices> + </domain> + """ + domain_mock = self.set_mock_vm("my_vm", xml) + domain_mock.OSType.return_value = "hvm" + self.mock_conn.defineXML.return_value = True + updatedev_mock = MagicMock(return_value=0) + domain_mock.updateDeviceFlags = updatedev_mock + self.mock_conn.listStoragePools.return_value = ["default"] + self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = ( + "<pool type='dir'/>" + ) + + ret = virt.update( + "my_vm", + disks=[ + { + "name": "system", + "pool": "default", + "backing_store_path": "/path/to/base.qcow2", + "backing_store_format": "qcow2", + }, + ], + ) + self.assertFalse(ret["definition"]) + self.assertFalse(ret["disk"]["attached"]) + self.assertFalse(ret["disk"]["detached"]) + + def test_update_removables(self): + """ + Test attaching, detaching, changing removable devices + """ + xml = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <disk type='network' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <source protocol='https' name='/dvd-image-1.iso'> + <host name='test-srv.local' port='80'/> + </source> + <backingStore/> + <target dev='hda' bus='ide'/> + <readonly/> + <alias name='ide0-0-0'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <disk type='file' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <target dev='hdb' bus='ide'/> + <readonly/> + <alias name='ide0-0-1'/> + <address type='drive' controller='0' bus='0' target='0' unit='1'/> + </disk> + <disk type='file' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <source file='/srv/dvd-image-2.iso'/> + <backingStore/> + <target dev='hdc' bus='ide'/> + <readonly/> + <alias name='ide0-0-2'/> + <address type='drive' controller='0' bus='0' target='0' unit='2'/> + </disk> + <disk type='file' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <source file='/srv/dvd-image-3.iso'/> + <backingStore/> + <target dev='hdd' bus='ide'/> + <readonly/> + <alias name='ide0-0-3'/> + <address type='drive' controller='0' bus='0' target='0' unit='3'/> + </disk> + <disk type='network' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <source protocol='https' name='/dvd-image-6.iso'> + <host name='test-srv.local' port='80'/> + </source> + <backingStore/> + <target dev='hde' bus='ide'/> + <readonly/> + </disk> + </devices> + </domain> + """ + domain_mock = self.set_mock_vm("my_vm", xml) + domain_mock.OSType.return_value = "hvm" + self.mock_conn.defineXML.return_value = True + updatedev_mock = MagicMock(return_value=0) + domain_mock.updateDeviceFlags = updatedev_mock + + ret = virt.update( + "my_vm", + disks=[ + { + "name": "dvd1", + "device": "cdrom", + "source_file": None, + "model": "ide", + }, + { + "name": "dvd2", + "device": "cdrom", + "source_file": "/srv/dvd-image-4.iso", + "model": "ide", + }, + { + "name": "dvd3", + "device": "cdrom", + "source_file": "/srv/dvd-image-2.iso", + "model": "ide", + }, + { + "name": "dvd4", + "device": "cdrom", + "source_file": "/srv/dvd-image-5.iso", + "model": "ide", + }, + { + "name": "dvd5", + "device": "cdrom", + "source_file": "/srv/dvd-image-6.iso", + "model": "ide", + }, + ], + ) + + self.assertTrue(ret["definition"]) + self.assertFalse(ret["disk"]["attached"]) + self.assertFalse(ret["disk"]["detached"]) + self.assertEqual( + [ + { + "type": "file", + "device": "cdrom", + "driver": { + "name": "qemu", + "type": "raw", + "cache": "none", + "io": "native", + }, + "backingStore": None, + "target": {"dev": "hda", "bus": "ide"}, + "readonly": None, + "alias": {"name": "ide0-0-0"}, + "address": { + "type": "drive", + "controller": "0", + "bus": "0", + "target": "0", + "unit": "0", + }, + }, + { + "type": "file", + "device": "cdrom", + "driver": { + "name": "qemu", + "type": "raw", + "cache": "none", + "io": "native", + }, + "target": {"dev": "hdb", "bus": "ide"}, + "readonly": None, + "alias": {"name": "ide0-0-1"}, + "address": { + "type": "drive", + "controller": "0", + "bus": "0", + "target": "0", + "unit": "1", + }, + "source": {"file": "/srv/dvd-image-4.iso"}, + }, + { + "type": "file", + "device": "cdrom", + "driver": { + "name": "qemu", + "type": "raw", + "cache": "none", + "io": "native", + }, + "backingStore": None, + "target": {"dev": "hdd", "bus": "ide"}, + "readonly": None, + "alias": {"name": "ide0-0-3"}, + "address": { + "type": "drive", + "controller": "0", + "bus": "0", + "target": "0", + "unit": "3", + }, + "source": {"file": "/srv/dvd-image-5.iso"}, + }, + { + "type": "file", + "device": "cdrom", + "driver": { + "name": "qemu", + "type": "raw", + "cache": "none", + "io": "native", + }, + "backingStore": None, + "target": {"dev": "hde", "bus": "ide"}, + "readonly": None, + "source": {"file": "/srv/dvd-image-6.iso"}, + }, + ], + [ + salt.utils.xmlutil.to_dict(ET.fromstring(disk), True) + for disk in ret["disk"]["updated"] + ], + ) + + def test_update_existing_boot_params(self): + """ + Test virt.update() with existing boot parameters. + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + xml_boot = """ + <domain type='kvm' id='8'> + <name>vm_with_boot_param</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + <kernel>/boot/oldkernel</kernel> + <initrd>/boot/initrdold.img</initrd> + <cmdline>console=ttyS0 ks=http://example.com/old/os/</cmdline> + <loader>/usr/share/old/OVMF_CODE.fd</loader> + <nvram>/usr/share/old/OVMF_VARS.ms.fd</nvram> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='{0}{1}vm_with_boot_param_system.qcow2'/> + <backingStore/> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='{0}{1}vm_with_boot_param_data.qcow2'/> + <backingStore/> + <target dev='vdb' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/> + </disk> + <interface type='network'> + <mac address='52:54:00:39:02:b1'/> + <source network='default' bridge='virbr0'/> + <target dev='vnet0'/> + <model type='virtio'/> + <alias name='net0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </interface> + <interface type='network'> + <mac address='52:54:00:39:02:b2'/> + <source network='oldnet' bridge='virbr1'/> + <target dev='vnet1'/> + <model type='virtio'/> + <alias name='net1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/> + </interface> + <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'> + <listen type='address' address='127.0.0.1'/> + </graphics> + <video> + <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> + <alias name='video0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </video> + </devices> + </domain> + """.format( + root_dir, os.sep + ) + domain_mock_boot = self.set_mock_vm("vm_with_boot_param", xml_boot) + domain_mock_boot.OSType = MagicMock(return_value="hvm") + define_mock_boot = MagicMock(return_value=True) + self.mock_conn.defineXML = define_mock_boot + boot_new = { + "kernel": "/root/new-vmlinuz", + "initrd": "/root/new-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/new/os/", + } + + uefi_boot_new = { + "loader": "/usr/share/new/OVMF_CODE.fd", + "nvram": "/usr/share/new/OVMF_VARS.ms.fd", + } + + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("vm_with_boot_param", boot=boot_new), + ) + setxml_boot = ET.fromstring(define_mock_boot.call_args[0][0]) + self.assertEqual( + setxml_boot.find("os").find("kernel").text, "/root/new-vmlinuz" + ) + self.assertEqual(setxml_boot.find("os").find("initrd").text, "/root/new-initrd") + self.assertEqual( + setxml_boot.find("os").find("cmdline").text, + "console=ttyS0 ks=http://example.com/new/os/", + ) + + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("vm_with_boot_param", boot=uefi_boot_new), + ) + + setxml = ET.fromstring(define_mock_boot.call_args[0][0]) + self.assertEqual( + setxml.find("os").find("loader").text, "/usr/share/new/OVMF_CODE.fd" + ) + self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes") + self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash") + self.assertEqual( + setxml.find("os").find("nvram").attrib["template"], + "/usr/share/new/OVMF_VARS.ms.fd", + ) + + kernel_none = { + "kernel": None, + "initrd": None, + "cmdline": None, + } + + uefi_none = {"loader": None, "nvram": None} + + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("vm_with_boot_param", boot=kernel_none), + ) + + setxml = ET.fromstring(define_mock_boot.call_args[0][0]) + self.assertEqual(setxml.find("os").find("kernel"), None) + self.assertEqual(setxml.find("os").find("initrd"), None) + self.assertEqual(setxml.find("os").find("cmdline"), None) + + self.assertEqual( + { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + }, + virt.update("vm_with_boot_param", boot=uefi_none), + ) + + setxml = ET.fromstring(define_mock_boot.call_args[0][0]) + self.assertEqual(setxml.find("os").find("loader"), None) + self.assertEqual(setxml.find("os").find("nvram"), None) def test_mixed_dict_and_list_as_profile_objects(self): - ''' + """ Test virt._nic_profile with mixed dictionaries and lists as input. - ''' - yaml_config = ''' + """ + yaml_config = """ virt: nic: new-listonly-profile: @@ -1484,30 +2576,28 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): eth1: bridge: br1 model: virtio - ''' + """ mock_config = salt.utils.yaml.safe_load(yaml_config) - with patch.dict(salt.modules.config.__opts__, mock_config): # pylint: disable=no-member + with patch.dict( + salt.modules.config.__opts__, mock_config # pylint: disable=no-member + ): - for name in six.iterkeys(mock_config['virt']['nic']): - profile = salt.modules.virt._nic_profile(name, 'kvm') + for name in six.iterkeys(mock_config["virt"]["nic"]): + profile = salt.modules.virt._nic_profile(name, "kvm") self.assertEqual(len(profile), 2) interface_attrs = profile[0] - self.assertIn('source', interface_attrs) - self.assertIn('type', interface_attrs) - self.assertIn('name', interface_attrs) - self.assertIn('model', interface_attrs) - self.assertEqual(interface_attrs['model'], 'virtio') - self.assertIn('mac', interface_attrs) - self.assertTrue( - re.match('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$', - interface_attrs['mac'], re.I)) + self.assertIn("source", interface_attrs) + self.assertIn("type", interface_attrs) + self.assertIn("name", interface_attrs) + self.assertIn("model", interface_attrs) + self.assertEqual(interface_attrs["model"], "virtio") def test_get_xml(self): - ''' + """ Test virt.get_xml() - ''' - xml = '''<domain type='kvm' id='7'> + """ + xml = """<domain type='kvm' id='7'> <name>test-vm</name> <devices> <graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'> @@ -1515,16 +2605,33 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </graphics> </devices> </domain> - ''' + """ domain = self.set_mock_vm("test-vm", xml) - self.assertEqual(xml, virt.get_xml('test-vm')) + self.assertEqual(xml, virt.get_xml("test-vm")) self.assertEqual(xml, virt.get_xml(domain)) + def test_get_loader(self): + """ + Test virt.get_loader() + """ + xml = """<domain type='kvm' id='7'> + <name>test-vm</name> + <os> + <loader readonly='yes' type='pflash'>/foo/bar</loader> + </os> + </domain> + """ + self.set_mock_vm("test-vm", xml) + + loader = virt.get_loader("test-vm") + self.assertEqual("/foo/bar", loader["path"]) + self.assertEqual("yes", loader["readonly"]) + def test_parse_qemu_img_info(self): - ''' + """ Make sure that qemu-img info output is properly parsed - ''' - qemu_infos = '''[{ + """ + qemu_infos = """[{ "snapshots": [ { "vm-clock-nsec": 0, @@ -1598,57 +2705,61 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): } }, "dirty-flag": false - }]''' + }]""" self.assertEqual( { - 'file': '/disks/test.qcow2', - 'file format': 'qcow2', - 'backing file': { - 'file': '/disks/mybacking.qcow2', - 'file format': 'qcow2', - 'disk size': 393744384, - 'virtual size': 25769803776, - 'cluster size': 65536, - 'backing file': { - 'file': '/disks/root.qcow2', - 'file format': 'qcow2', - 'disk size': 196872192, - 'virtual size': 25769803776, - 'cluster size': 65536, - } + "file": "/disks/test.qcow2", + "file format": "qcow2", + "backing file": { + "file": "/disks/mybacking.qcow2", + "file format": "qcow2", + "disk size": 393744384, + "virtual size": 25769803776, + "cluster size": 65536, + "backing file": { + "file": "/disks/root.qcow2", + "file format": "qcow2", + "disk size": 196872192, + "virtual size": 25769803776, + "cluster size": 65536, + }, }, - 'disk size': 217088, - 'virtual size': 25769803776, - 'cluster size': 65536, - 'snapshots': [ + "disk size": 217088, + "virtual size": 25769803776, + "cluster size": 65536, + "snapshots": [ { - 'id': '1', - 'tag': 'first-snap', - 'vmsize': 1234, - 'date': datetime.datetime.fromtimestamp( - float("{}.{}".format(1528877587, 380589000))).isoformat(), - 'vmclock': '00:00:00' + "id": "1", + "tag": "first-snap", + "vmsize": 1234, + "date": datetime.datetime.fromtimestamp( + float("{}.{}".format(1528877587, 380589000)) + ).isoformat(), + "vmclock": "00:00:00", }, { - 'id': '2', - 'tag': 'second snap', - 'vmsize': 4567, - 'date': datetime.datetime.fromtimestamp( - float("{}.{}".format(1528877592, 933509000))).isoformat(), - 'vmclock': '00:00:00' - } + "id": "2", + "tag": "second snap", + "vmsize": 4567, + "date": datetime.datetime.fromtimestamp( + float("{}.{}".format(1528877592, 933509000)) + ).isoformat(), + "vmclock": "00:00:00", + }, ], - }, virt._parse_qemu_img_info(qemu_infos)) + }, + virt._parse_qemu_img_info(qemu_infos), + ) - @patch('salt.modules.virt.stop', return_value=True) - @patch('salt.modules.virt.undefine') - @patch('os.remove') + @patch("salt.modules.virt.stop", return_value=True) + @patch("salt.modules.virt.undefine") + @patch("os.remove") def test_purge_default(self, mock_remove, mock_undefine, mock_stop): - ''' + """ Test virt.purge() with default parameters - ''' - xml = '''<domain type='kvm' id='7'> + """ + xml = """<domain type='kvm' id='7'> <name>test-vm</name> <devices> <disk type='file' device='disk'> @@ -1664,10 +2775,10 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </disk> </devices> </domain> - ''' + """ self.set_mock_vm("test-vm", xml) - qemu_infos = '''[{ + qemu_infos = """[{ "virtual-size": 25769803776, "filename": "/disks/test.qcow2", "cluster-size": 65536, @@ -1683,23 +2794,157 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): } }, "dirty-flag": false - }]''' + }]""" + + self.mock_popen.communicate.return_value = [ + qemu_infos + ] # pylint: disable=no-member + + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}): + res = virt.purge("test-vm") + self.assertTrue(res) + mock_remove.assert_called_once() + mock_remove.assert_any_call("/disks/test.qcow2") + + @patch("salt.modules.virt.stop", return_value=True) + @patch("salt.modules.virt.undefine") + def test_purge_volumes(self, mock_undefine, mock_stop): + """ + Test virt.purge() with volume disks + """ + xml = """<domain type='kvm' id='7'> + <name>test-vm</name> + <devices> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2' cache='none' io='native'/> + <source pool='default' volume='vm05_system'/> + <backingStore type='file' index='1'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/vm04_system.qcow2'/> + <backingStore type='file' index='2'> + <format type='qcow2'/> + <source file='/var/testsuite-data/disk-image-template.qcow2'/> + <backingStore/> + </backingStore> + </backingStore> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + </devices> + </domain> + """ + self.set_mock_vm("test-vm", xml) - self.mock_popen.communicate.return_value = [qemu_infos] # pylint: disable=no-member + pool_mock = MagicMock() + pool_mock.storageVolLookupByName.return_value.info.return_value = [ + 0, + 1234567, + 12345, + ] + pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [ + """ + <volume type='file'> + <name>vm05_system</name> + <target> + <path>/var/lib/libvirt/images/vm05_system</path> + <format type='qcow2'/> + </target> + <backingStore> + <path>/var/lib/libvirt/images/vm04_system.qcow2</path> + <format type='qcow2'/> + </backingStore> + </volume> + """, + ] + pool_mock.listVolumes.return_value = ["vm05_system", "vm04_system.qcow2"] + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + self.mock_conn.listStoragePools.return_value = ["default"] + + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}): + res = virt.purge("test-vm") + self.assertTrue(res) + pool_mock.storageVolLookupByName.return_value.delete.assert_called_once() + + @patch("salt.modules.virt.stop", return_value=True) + @patch("salt.modules.virt.undefine") + def test_purge_rbd(self, mock_undefine, mock_stop): + """ + Test virt.purge() with RBD disks + """ + xml = """<domain type='kvm' id='7'> + <name>test-vm</name> + <devices> + <disk type="network" device="disk"> + <driver name='raw' type='qcow2'/> + <source protocol='rbd' name='libvirt-pool/my_vm_data2'> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <auth username='libvirt'> + <secret type='ceph' usage='pool_test-rbd'/> + </auth> + </source> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/> + </disk> + </devices> + </domain> + """ + self.set_mock_vm("test-vm", xml) - res = virt.purge('test-vm') - self.assertTrue(res) - mock_remove.assert_any_call('/disks/test.qcow2') - mock_remove.assert_any_call('/disks/test-cdrom.iso') - - @patch('salt.modules.virt.stop', return_value=True) - @patch('salt.modules.virt.undefine') - @patch('os.remove') - def test_purge_noremovable(self, mock_remove, mock_undefine, mock_stop): - ''' - Test virt.purge(removables=False) - ''' - xml = '''<domain type='kvm' id='7'> + pool_mock = MagicMock() + pool_mock.storageVolLookupByName.return_value.info.return_value = [ + 0, + 1234567, + 12345, + ] + pool_mock.XMLDesc.return_value = """ + <pool type='rbd'> + <name>test-ses</name> + <source> + <host name='ses2.tf.local'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret usage='pool_test-ses'/> + </auth> + </source> + </pool> + """ + pool_mock.name.return_value = "test-ses" + pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [ + """ + <volume type='network'> + <name>my_vm_data2</name> + <source> + </source> + <capacity unit='bytes'>536870912</capacity> + <allocation unit='bytes'>0</allocation> + <target> + <path>libvirt-pool/my_vm_data2</path> + <format type='raw'/> + </target> + </volume> + """, + ] + pool_mock.listVolumes.return_value = ["my_vm_data2"] + self.mock_conn.listAllStoragePools.return_value = [pool_mock] + self.mock_conn.listStoragePools.return_value = ["test-ses"] + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}): + res = virt.purge("test-vm") + self.assertTrue(res) + pool_mock.storageVolLookupByName.return_value.delete.assert_called_once() + + @patch("salt.modules.virt.stop", return_value=True) + @patch("salt.modules.virt.undefine") + @patch("os.remove") + def test_purge_removable(self, mock_remove, mock_undefine, mock_stop): + """ + Test virt.purge(removables=True) + """ + xml = """<domain type="kvm" id="7"> <name>test-vm</name> <devices> <disk type='file' device='disk'> @@ -1721,10 +2966,10 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </disk> </devices> </domain> - ''' + """ self.set_mock_vm("test-vm", xml) - qemu_infos = '''[{ + qemu_infos = """[{ "virtual-size": 25769803776, "filename": "/disks/test.qcow2", "cluster-size": 65536, @@ -1740,20 +2985,23 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): } }, "dirty-flag": false - }]''' + }]""" - self.mock_popen.communicate.return_value = [qemu_infos] # pylint: disable=no-member + self.mock_popen.communicate.return_value = [ + qemu_infos + ] # pylint: disable=no-member - res = virt.purge('test-vm', removables=False) - self.assertTrue(res) - mock_remove.assert_called_once() - mock_remove.assert_any_call('/disks/test.qcow2') + with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}): + res = virt.purge("test-vm", removables=True) + self.assertTrue(res) + mock_remove.assert_any_call("/disks/test.qcow2") + mock_remove.assert_any_call("/disks/test-cdrom.iso") def test_capabilities(self): - ''' + """ Test the virt.capabilities parsing - ''' - xml = ''' + """ + xml = """ <capabilities> <host> <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid> @@ -1881,198 +3129,258 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </guest> </capabilities> - ''' + """ self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member caps = virt.capabilities() expected = { - 'host': { - 'uuid': '44454c4c-3400-105a-8033-b3c04f4b344a', - 'cpu': { - 'arch': 'x86_64', - 'model': 'Nehalem', - 'vendor': 'Intel', - 'microcode': '25', - 'sockets': 1, - 'cores': 4, - 'threads': 2, - 'features': ['vme', 'ds', 'acpi'], - 'pages': [{'size': '4 KiB'}, {'size': '2048 KiB'}] - }, - 'power_management': ['suspend_mem', 'suspend_disk', 'suspend_hybrid'], - 'migration': { - 'live': True, - 'transports': ['tcp', 'rdma'] + "host": { + "uuid": "44454c4c-3400-105a-8033-b3c04f4b344a", + "cpu": { + "arch": "x86_64", + "model": "Nehalem", + "vendor": "Intel", + "microcode": "25", + "sockets": 1, + "cores": 4, + "threads": 2, + "features": ["vme", "ds", "acpi"], + "pages": [{"size": "4 KiB"}, {"size": "2048 KiB"}], }, - 'topology': { - 'cells': [ + "power_management": ["suspend_mem", "suspend_disk", "suspend_hybrid"], + "migration": {"live": True, "transports": ["tcp", "rdma"]}, + "topology": { + "cells": [ { - 'id': 0, - 'memory': '12367120 KiB', - 'pages': [ - {'size': '4 KiB', 'available': 3091780}, - {'size': '2048 KiB', 'available': 0} + "id": 0, + "memory": "12367120 KiB", + "pages": [ + {"size": "4 KiB", "available": 3091780}, + {"size": "2048 KiB", "available": 0}, + ], + "distances": {0: 10}, + "cpus": [ + { + "id": 0, + "socket_id": 0, + "core_id": 0, + "siblings": "0,4", + }, + { + "id": 1, + "socket_id": 0, + "core_id": 1, + "siblings": "1,5", + }, + { + "id": 2, + "socket_id": 0, + "core_id": 2, + "siblings": "2,6", + }, + { + "id": 3, + "socket_id": 0, + "core_id": 3, + "siblings": "3,7", + }, + { + "id": 4, + "socket_id": 0, + "core_id": 0, + "siblings": "0,4", + }, + { + "id": 5, + "socket_id": 0, + "core_id": 1, + "siblings": "1,5", + }, + { + "id": 6, + "socket_id": 0, + "core_id": 2, + "siblings": "2,6", + }, + { + "id": 7, + "socket_id": 0, + "core_id": 3, + "siblings": "3,7", + }, ], - 'distances': { - 0: 10, - }, - 'cpus': [ - {'id': 0, 'socket_id': 0, 'core_id': 0, 'siblings': '0,4'}, - {'id': 1, 'socket_id': 0, 'core_id': 1, 'siblings': '1,5'}, - {'id': 2, 'socket_id': 0, 'core_id': 2, 'siblings': '2,6'}, - {'id': 3, 'socket_id': 0, 'core_id': 3, 'siblings': '3,7'}, - {'id': 4, 'socket_id': 0, 'core_id': 0, 'siblings': '0,4'}, - {'id': 5, 'socket_id': 0, 'core_id': 1, 'siblings': '1,5'}, - {'id': 6, 'socket_id': 0, 'core_id': 2, 'siblings': '2,6'}, - {'id': 7, 'socket_id': 0, 'core_id': 3, 'siblings': '3,7'} - ] } ] }, - 'cache': { - 'banks': [ - {'id': 0, 'level': 3, 'type': 'both', 'size': '8 MiB', 'cpus': '0-7'} + "cache": { + "banks": [ + { + "id": 0, + "level": 3, + "type": "both", + "size": "8 MiB", + "cpus": "0-7", + } ] }, - 'security': [ - {'model': 'apparmor', 'doi': '0', 'baselabels': []}, - {'model': 'dac', 'doi': '0', 'baselabels': [ - {'type': 'kvm', 'label': '+487:+486'}, - {'type': 'qemu', 'label': '+487:+486'} - ]} - ] + "security": [ + {"model": "apparmor", "doi": "0", "baselabels": []}, + { + "model": "dac", + "doi": "0", + "baselabels": [ + {"type": "kvm", "label": "+487:+486"}, + {"type": "qemu", "label": "+487:+486"}, + ], + }, + ], }, - 'guests': [ + "guests": [ { - 'os_type': 'hvm', - 'arch': { - 'name': 'i686', - 'wordsize': 32, - 'emulator': '/usr/bin/qemu-system-i386', - 'machines': { - 'pc-i440fx-2.6': {'maxcpus': 255, 'alternate_names': ['pc']}, - 'pc-0.12': {'maxcpus': 255, 'alternate_names': []} + "os_type": "hvm", + "arch": { + "name": "i686", + "wordsize": 32, + "emulator": "/usr/bin/qemu-system-i386", + "machines": { + "pc-i440fx-2.6": { + "maxcpus": 255, + "alternate_names": ["pc"], + }, + "pc-0.12": {"maxcpus": 255, "alternate_names": []}, }, - 'domains': { - 'qemu': { - 'emulator': None, - 'machines': {} + "domains": { + "qemu": {"emulator": None, "machines": {}}, + "kvm": { + "emulator": "/usr/bin/qemu-kvm", + "machines": { + "pc-i440fx-2.6": { + "maxcpus": 255, + "alternate_names": ["pc"], + }, + "pc-0.12": {"maxcpus": 255, "alternate_names": []}, + }, }, - 'kvm': { - 'emulator': '/usr/bin/qemu-kvm', - 'machines': { - 'pc-i440fx-2.6': {'maxcpus': 255, 'alternate_names': ['pc']}, - 'pc-0.12': {'maxcpus': 255, 'alternate_names': []} - } - } - } + }, + }, + "features": { + "cpuselection": {"default": True, "toggle": False}, + "deviceboot": {"default": True, "toggle": False}, + "disksnapshot": {"default": True, "toggle": False}, + "acpi": {"default": True, "toggle": True}, + "apic": {"default": True, "toggle": False}, + "pae": {"default": True, "toggle": False}, + "nonpae": {"default": True, "toggle": False}, }, - 'features': { - 'cpuselection': {'default': True, 'toggle': False}, - 'deviceboot': {'default': True, 'toggle': False}, - 'disksnapshot': {'default': True, 'toggle': False}, - 'acpi': {'default': True, 'toggle': True}, - 'apic': {'default': True, 'toggle': False}, - 'pae': {'default': True, 'toggle': False}, - 'nonpae': {'default': True, 'toggle': False} - } }, { - 'os_type': 'hvm', - 'arch': { - 'name': 'x86_64', - 'wordsize': 64, - 'emulator': '/usr/bin/qemu-system-x86_64', - 'machines': { - 'pc-i440fx-2.6': {'maxcpus': 255, 'alternate_names': ['pc']}, - 'pc-0.12': {'maxcpus': 255, 'alternate_names': []} + "os_type": "hvm", + "arch": { + "name": "x86_64", + "wordsize": 64, + "emulator": "/usr/bin/qemu-system-x86_64", + "machines": { + "pc-i440fx-2.6": { + "maxcpus": 255, + "alternate_names": ["pc"], + }, + "pc-0.12": {"maxcpus": 255, "alternate_names": []}, }, - 'domains': { - 'qemu': { - 'emulator': None, - 'machines': {} + "domains": { + "qemu": {"emulator": None, "machines": {}}, + "kvm": { + "emulator": "/usr/bin/qemu-kvm", + "machines": { + "pc-i440fx-2.6": { + "maxcpus": 255, + "alternate_names": ["pc"], + }, + "pc-0.12": {"maxcpus": 255, "alternate_names": []}, + }, }, - 'kvm': { - 'emulator': '/usr/bin/qemu-kvm', - 'machines': { - 'pc-i440fx-2.6': {'maxcpus': 255, 'alternate_names': ['pc']}, - 'pc-0.12': {'maxcpus': 255, 'alternate_names': []} - } - } - } + }, + }, + "features": { + "cpuselection": {"default": True, "toggle": False}, + "deviceboot": {"default": True, "toggle": False}, + "disksnapshot": {"default": True, "toggle": False}, + "acpi": {"default": True, "toggle": True}, + "apic": {"default": True, "toggle": False}, }, - 'features': { - 'cpuselection': {'default': True, 'toggle': False}, - 'deviceboot': {'default': True, 'toggle': False}, - 'disksnapshot': {'default': True, 'toggle': False}, - 'acpi': {'default': True, 'toggle': True}, - 'apic': {'default': True, 'toggle': False} - } }, { - 'os_type': 'xen', - 'arch': { - 'name': 'x86_64', - 'wordsize': 64, - 'emulator': '/usr/bin/qemu-system-x86_64', - 'machines': { - 'xenpv': {'alternate_names': []} - }, - 'domains': { - 'xen': { - 'emulator': None, - 'machines': {} - } - } - } - } - ] + "os_type": "xen", + "arch": { + "name": "x86_64", + "wordsize": 64, + "emulator": "/usr/bin/qemu-system-x86_64", + "machines": {"xenpv": {"alternate_names": []}}, + "domains": {"xen": {"emulator": None, "machines": {}}}, + }, + }, + ], } self.assertEqual(expected, caps) def test_network(self): - ''' + """ Test virt._get_net_xml() - ''' - xml_data = virt._gen_net_xml('network', 'main', 'bridge', 'openvswitch') + """ + xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch") root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'network') - self.assertEqual(root.find('bridge').attrib['name'], 'main') - self.assertEqual(root.find('forward').attrib['mode'], 'bridge') - self.assertEqual(root.find('virtualport').attrib['type'], 'openvswitch') + self.assertEqual(root.find("name").text, "network") + self.assertEqual(root.find("bridge").attrib["name"], "main") + self.assertEqual(root.find("forward").attrib["mode"], "bridge") + self.assertEqual(root.find("virtualport").attrib["type"], "openvswitch") def test_network_nat(self): - ''' + """ Test virt._get_net_xml() in a nat setup - ''' - xml_data = virt._gen_net_xml('network', 'main', 'nat', None, ip_configs=[ - { - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - } - ]) + """ + xml_data = virt._gen_net_xml( + "network", + "main", + "nat", + None, + ip_configs=[ + { + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + } + ], + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'network') - self.assertEqual(root.find('bridge').attrib['name'], 'main') - self.assertEqual(root.find('forward').attrib['mode'], 'nat') - self.assertEqual(root.find("./ip[@address='192.168.2.0']").attrib['prefix'], '24') - self.assertEqual(root.find("./ip[@address='192.168.2.0']").attrib['family'], 'ipv4') + self.assertEqual(root.find("name").text, "network") + self.assertEqual(root.find("bridge").attrib["name"], "main") + self.assertEqual(root.find("forward").attrib["mode"], "nat") self.assertEqual( - root.find("./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.10']").attrib['end'], - '192.168.2.25') + root.find("./ip[@address='192.168.2.0']").attrib["prefix"], "24" + ) self.assertEqual( - root.find("./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.110']").attrib['end'], - '192.168.2.125') + root.find("./ip[@address='192.168.2.0']").attrib["family"], "ipv4" + ) + self.assertEqual( + root.find( + "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.10']" + ).attrib["end"], + "192.168.2.25", + ) + self.assertEqual( + root.find( + "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.110']" + ).attrib["end"], + "192.168.2.125", + ) def test_domain_capabilities(self): - ''' + """ Test the virt.domain_capabilities parsing - ''' - xml = ''' + """ + xml = """ <domainCapabilities> <path>/usr/bin/qemu-system-aarch64</path> <domain>kvm</domain> @@ -2169,84 +3477,67 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <vmcoreinfo supported='yes'/> </features> </domainCapabilities> - ''' + """ - self.mock_conn.getDomainCapabilities.return_value = xml # pylint: disable=no-member + self.mock_conn.getDomainCapabilities.return_value = ( + xml # pylint: disable=no-member + ) caps = virt.domain_capabilities() expected = { - 'emulator': '/usr/bin/qemu-system-aarch64', - 'domain': 'kvm', - 'machine': 'virt-2.12', - 'arch': 'aarch64', - 'max_vcpus': 255, - 'iothreads': True, - 'os': { - 'loader': { - 'type': ['rom', 'pflash'], - 'readonly': ['yes', 'no'], - 'values': [ - '/usr/share/AAVMF/AAVMF_CODE.fd', - '/usr/share/AAVMF/AAVMF32_CODE.fd', - '/usr/share/OVMF/OVMF_CODE.fd' - ] + "emulator": "/usr/bin/qemu-system-aarch64", + "domain": "kvm", + "machine": "virt-2.12", + "arch": "aarch64", + "max_vcpus": 255, + "iothreads": True, + "os": { + "loader": { + "type": ["rom", "pflash"], + "readonly": ["yes", "no"], + "values": [ + "/usr/share/AAVMF/AAVMF_CODE.fd", + "/usr/share/AAVMF/AAVMF32_CODE.fd", + "/usr/share/OVMF/OVMF_CODE.fd", + ], } }, - 'cpu': { - 'host-passthrough': True, - 'host-model': { - 'model': { - 'name': 'sample-cpu', - 'fallback': 'forbid' - }, - 'vendor': 'ACME', - 'features': { - 'vme': 'require', - 'ss': 'require' - } + "cpu": { + "host-passthrough": True, + "host-model": { + "model": {"name": "sample-cpu", "fallback": "forbid"}, + "vendor": "ACME", + "features": {"vme": "require", "ss": "require"}, }, - 'custom': { - 'models': { - 'pxa262': 'unknown', - 'pxa270-a0': 'yes', - 'arm1136': 'no' - } - } - }, - 'devices': { - 'disk': { - 'diskDevice': ['disk', 'cdrom', 'floppy', 'lun'], - 'bus': ['fdc', 'scsi', 'virtio', 'usb', 'sata'], + "custom": { + "models": {"pxa262": "unknown", "pxa270-a0": "yes", "arm1136": "no"} }, - 'graphics': { - 'type': ['sdl', 'vnc'] + }, + "devices": { + "disk": { + "diskDevice": ["disk", "cdrom", "floppy", "lun"], + "bus": ["fdc", "scsi", "virtio", "usb", "sata"], }, - 'video': { - 'modelType': ['vga', 'virtio'] + "graphics": {"type": ["sdl", "vnc"]}, + "video": {"modelType": ["vga", "virtio"]}, + "hostdev": { + "mode": ["subsystem"], + "startupPolicy": ["default", "mandatory", "requisite", "optional"], + "subsysType": ["usb", "pci", "scsi"], + "capsType": [], + "pciBackend": ["default", "kvm", "vfio"], }, - 'hostdev': { - 'mode': ['subsystem'], - 'startupPolicy': ['default', 'mandatory', 'requisite', 'optional'], - 'subsysType': ['usb', 'pci', 'scsi'], - 'capsType': [], - 'pciBackend': ['default', 'kvm', 'vfio'] - } }, - 'features': { - 'gic': { - 'version': ['3'] - }, - 'vmcoreinfo': {} - } + "features": {"gic": {"version": ["3"]}, "vmcoreinfo": {}}, } self.assertEqual(expected, caps) def test_all_capabilities(self): - ''' + """ Test the virt.domain_capabilities default output - ''' - domainXml = ''' + """ + domainXml = """ <domainCapabilities> <path>/usr/bin/qemu-system-x86_64</path> <domain>kvm</domain> @@ -2255,8 +3546,8 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <vcpu max='255'/> <iothreads supported='yes'/> </domainCapabilities> - ''' - hostXml = ''' + """ + hostXml = """ <capabilities> <host> <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid> @@ -2286,100 +3577,115 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </arch> </guest> </capabilities> - ''' + """ # pylint: disable=no-member self.mock_conn.getCapabilities.return_value = hostXml self.mock_conn.getDomainCapabilities.side_effect = [ - domainXml, domainXml.replace('<domain>kvm', '<domain>qemu')] + domainXml, + domainXml.replace("<domain>kvm", "<domain>qemu"), + ] # pylint: enable=no-member caps = virt.all_capabilities() - self.assertEqual('44454c4c-3400-105a-8033-b3c04f4b344a', caps['host']['host']['uuid']) - self.assertEqual(set(['qemu', 'kvm']), set([domainCaps['domain'] for domainCaps in caps['domains']])) + self.assertEqual( + "44454c4c-3400-105a-8033-b3c04f4b344a", caps["host"]["host"]["uuid"] + ) + self.assertEqual( + set(["qemu", "kvm"]), + set([domainCaps["domain"] for domainCaps in caps["domains"]]), + ) def test_network_tag(self): - ''' + """ Test virt._get_net_xml() with VLAN tag - ''' - xml_data = virt._gen_net_xml('network', 'main', 'bridge', 'openvswitch', 1001) + """ + xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch", 1001) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'network') - self.assertEqual(root.find('bridge').attrib['name'], 'main') - self.assertEqual(root.find('forward').attrib['mode'], 'bridge') - self.assertEqual(root.find('virtualport').attrib['type'], 'openvswitch') - self.assertEqual(root.find('vlan/tag').attrib['id'], '1001') + self.assertEqual(root.find("name").text, "network") + self.assertEqual(root.find("bridge").attrib["name"], "main") + self.assertEqual(root.find("forward").attrib["mode"], "bridge") + self.assertEqual(root.find("virtualport").attrib["type"], "openvswitch") + self.assertEqual(root.find("vlan/tag").attrib["id"], "1001") def test_list_networks(self): - ''' + """ Test virt.list_networks() - ''' - names = ['net1', 'default', 'net2'] + """ + names = ["net1", "default", "net2"] net_mocks = [MagicMock(), MagicMock(), MagicMock()] for i, value in enumerate(names): net_mocks[i].name.return_value = value - self.mock_conn.listAllNetworks.return_value = net_mocks # pylint: disable=no-member + self.mock_conn.listAllNetworks.return_value = ( + net_mocks # pylint: disable=no-member + ) actual = virt.list_networks() self.assertEqual(names, actual) def test_network_info(self): - ''' + """ Test virt.network_info() - ''' + """ self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1 net_mock = MagicMock() # pylint: disable=no-member - net_mock.name.return_value = 'foo' - net_mock.UUIDString.return_value = 'some-uuid' - net_mock.bridgeName.return_value = 'br0' + net_mock.name.return_value = "foo" + net_mock.UUIDString.return_value = "some-uuid" + net_mock.bridgeName.return_value = "br0" net_mock.autostart.return_value = True net_mock.isActive.return_value = False net_mock.isPersistent.return_value = True net_mock.DHCPLeases.return_value = [ { - 'iface': 'virbr0', - 'expirytime': 1527757552, - 'type': 0, - 'mac': '52:54:00:01:71:bd', - 'ipaddr': '192.168.122.45', - 'prefix': 24, - 'hostname': 'py3-test', - 'clientid': '01:52:54:00:01:71:bd', - 'iaid': None + "iface": "virbr0", + "expirytime": 1527757552, + "type": 0, + "mac": "52:54:00:01:71:bd", + "ipaddr": "192.168.122.45", + "prefix": 24, + "hostname": "py3-test", + "clientid": "01:52:54:00:01:71:bd", + "iaid": None, } ] self.mock_conn.listAllNetworks.return_value = [net_mock] # pylint: enable=no-member - net = virt.network_info('foo') - self.assertEqual({'foo': { - 'uuid': 'some-uuid', - 'bridge': 'br0', - 'autostart': True, - 'active': False, - 'persistent': True, - 'leases': [ - { - 'iface': 'virbr0', - 'expirytime': 1527757552, - 'type': 'ipv4', - 'mac': '52:54:00:01:71:bd', - 'ipaddr': '192.168.122.45', - 'prefix': 24, - 'hostname': 'py3-test', - 'clientid': '01:52:54:00:01:71:bd', - 'iaid': None + net = virt.network_info("foo") + self.assertEqual( + { + "foo": { + "uuid": "some-uuid", + "bridge": "br0", + "autostart": True, + "active": False, + "persistent": True, + "leases": [ + { + "iface": "virbr0", + "expirytime": 1527757552, + "type": "ipv4", + "mac": "52:54:00:01:71:bd", + "ipaddr": "192.168.122.45", + "prefix": 24, + "hostname": "py3-test", + "clientid": "01:52:54:00:01:71:bd", + "iaid": None, + } + ], } - ]}}, net) + }, + net, + ) def test_network_info_all(self): - ''' + """ Test virt.network_info() - ''' + """ self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1 @@ -2388,9 +3694,9 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): for i in range(2): net_mock = MagicMock() - net_mock.name.return_value = 'net{0}'.format(i) - net_mock.UUIDString.return_value = 'some-uuid' - net_mock.bridgeName.return_value = 'br{0}'.format(i) + net_mock.name.return_value = "net{0}".format(i) + net_mock.UUIDString.return_value = "some-uuid" + net_mock.bridgeName.return_value = "br{0}".format(i) net_mock.autostart.return_value = True net_mock.isActive.return_value = False net_mock.isPersistent.return_value = True @@ -2400,194 +3706,223 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # pylint: enable=no-member net = virt.network_info() - self.assertEqual({ - 'net0': - { - 'uuid': 'some-uuid', - 'bridge': 'br0', - 'autostart': True, - 'active': False, - 'persistent': True, - 'leases': [] - }, 'net1': + self.assertEqual( { - 'uuid': 'some-uuid', - 'bridge': 'br1', - 'autostart': True, - 'active': False, - 'persistent': True, - 'leases': [] - } - }, net) + "net0": { + "uuid": "some-uuid", + "bridge": "br0", + "autostart": True, + "active": False, + "persistent": True, + "leases": [], + }, + "net1": { + "uuid": "some-uuid", + "bridge": "br1", + "autostart": True, + "active": False, + "persistent": True, + "leases": [], + }, + }, + net, + ) def test_network_info_notfound(self): - ''' + """ Test virt.network_info() when the network can't be found - ''' + """ # pylint: disable=no-member self.mock_conn.listAllNetworks.return_value = [] # pylint: enable=no-member - net = virt.network_info('foo') + net = virt.network_info("foo") self.assertEqual({}, net) def test_network_get_xml(self): - ''' + """ Test virt.network_get_xml - ''' + """ network_mock = MagicMock() - network_mock.XMLDesc.return_value = '<net>Raw XML</net>' + network_mock.XMLDesc.return_value = "<net>Raw XML</net>" self.mock_conn.networkLookupByName.return_value = network_mock - self.assertEqual('<net>Raw XML</net>', virt.network_get_xml('default')) + self.assertEqual("<net>Raw XML</net>", virt.network_get_xml("default")) def test_pool(self): - ''' + """ Test virt._gen_pool_xml() - ''' - xml_data = virt._gen_pool_xml('pool', 'logical', '/dev/base') + """ + xml_data = virt._gen_pool_xml("pool", "logical", "/dev/base") root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'logical') - self.assertEqual(root.find('target/path').text, '/dev/base') + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "logical") + self.assertEqual(root.find("target/path").text, "/dev/base") def test_pool_with_source(self): - ''' + """ Test virt._gen_pool_xml() with a source device - ''' - xml_data = virt._gen_pool_xml('pool', 'logical', '/dev/base', source_devices=[{'path': '/dev/sda'}]) + """ + xml_data = virt._gen_pool_xml( + "pool", "logical", "/dev/base", source_devices=[{"path": "/dev/sda"}] + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'logical') - self.assertEqual(root.find('target/path').text, '/dev/base') - self.assertEqual(root.find('source/device').attrib['path'], '/dev/sda') + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "logical") + self.assertEqual(root.find("target/path").text, "/dev/base") + self.assertEqual(root.find("source/device").attrib["path"], "/dev/sda") def test_pool_with_scsi(self): - ''' + """ Test virt._gen_pool_xml() with a SCSI source - ''' - xml_data = virt._gen_pool_xml('pool', - 'scsi', - '/dev/disk/by-path', - source_devices=[{'path': '/dev/sda'}], - source_adapter={ - 'type': 'scsi_host', - 'parent_address': { - 'unique_id': 5, - 'address': { - 'domain': '0x0000', - 'bus': '0x00', - 'slot': '0x1f', - 'function': '0x2' - } - } - }, - source_name='srcname') + """ + xml_data = virt._gen_pool_xml( + "pool", + "scsi", + "/dev/disk/by-path", + source_devices=[{"path": "/dev/sda"}], + source_adapter={ + "type": "scsi_host", + "parent_address": { + "unique_id": 5, + "address": { + "domain": "0x0000", + "bus": "0x00", + "slot": "0x1f", + "function": "0x2", + }, + }, + }, + source_name="srcname", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'scsi') - self.assertEqual(root.find('target/path').text, '/dev/disk/by-path') - self.assertEqual(root.find('source/device'), None) - self.assertEqual(root.find('source/name'), None) - self.assertEqual(root.find('source/adapter').attrib['type'], 'scsi_host') - self.assertEqual(root.find('source/adapter/parentaddr').attrib['unique_id'], '5') - self.assertEqual(root.find('source/adapter/parentaddr/address').attrib['domain'], '0x0000') - self.assertEqual(root.find('source/adapter/parentaddr/address').attrib['bus'], '0x00') - self.assertEqual(root.find('source/adapter/parentaddr/address').attrib['slot'], '0x1f') - self.assertEqual(root.find('source/adapter/parentaddr/address').attrib['function'], '0x2') + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "scsi") + self.assertEqual(root.find("target/path").text, "/dev/disk/by-path") + self.assertEqual(root.find("source/device"), None) + self.assertEqual(root.find("source/name"), None) + self.assertEqual(root.find("source/adapter").attrib["type"], "scsi_host") + self.assertEqual( + root.find("source/adapter/parentaddr").attrib["unique_id"], "5" + ) + self.assertEqual( + root.find("source/adapter/parentaddr/address").attrib["domain"], "0x0000" + ) + self.assertEqual( + root.find("source/adapter/parentaddr/address").attrib["bus"], "0x00" + ) + self.assertEqual( + root.find("source/adapter/parentaddr/address").attrib["slot"], "0x1f" + ) + self.assertEqual( + root.find("source/adapter/parentaddr/address").attrib["function"], "0x2" + ) def test_pool_with_rbd(self): - ''' + """ Test virt._gen_pool_xml() with an RBD source - ''' - xml_data = virt._gen_pool_xml('pool', - 'rbd', - source_devices=[{'path': '/dev/sda'}], - source_hosts=['1.2.3.4', 'my.ceph.monitor:69'], - source_auth={ - 'type': 'ceph', - 'username': 'admin', - 'secret': { - 'type': 'uuid', - 'value': 'someuuid' - } - }, - source_name='srcname', - source_adapter={'type': 'scsi_host', 'name': 'host0'}, - source_dir='/some/dir', - source_format='fmt') + """ + xml_data = virt._gen_pool_xml( + "pool", + "rbd", + source_devices=[{"path": "/dev/sda"}], + source_hosts=["1.2.3.4", "my.ceph.monitor:69"], + source_auth={ + "type": "ceph", + "username": "admin", + "secret": {"type": "uuid", "value": "someuuid"}, + }, + source_name="srcname", + source_adapter={"type": "scsi_host", "name": "host0"}, + source_dir="/some/dir", + source_format="fmt", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'rbd') - self.assertEqual(root.find('target'), None) - self.assertEqual(root.find('source/device'), None) - self.assertEqual(root.find('source/name').text, 'srcname') - self.assertEqual(root.find('source/adapter'), None) - self.assertEqual(root.find('source/dir'), None) - self.assertEqual(root.find('source/format'), None) - self.assertEqual(root.findall('source/host')[0].attrib['name'], '1.2.3.4') - self.assertTrue('port' not in root.findall('source/host')[0].attrib) - self.assertEqual(root.findall('source/host')[1].attrib['name'], 'my.ceph.monitor') - self.assertEqual(root.findall('source/host')[1].attrib['port'], '69') - self.assertEqual(root.find('source/auth').attrib['type'], 'ceph') - self.assertEqual(root.find('source/auth').attrib['username'], 'admin') - self.assertEqual(root.find('source/auth/secret').attrib['uuid'], 'someuuid') + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "rbd") + self.assertEqual(root.find("target"), None) + self.assertEqual(root.find("source/device"), None) + self.assertEqual(root.find("source/name").text, "srcname") + self.assertEqual(root.find("source/adapter"), None) + self.assertEqual(root.find("source/dir"), None) + self.assertEqual(root.find("source/format"), None) + self.assertEqual(root.findall("source/host")[0].attrib["name"], "1.2.3.4") + self.assertTrue("port" not in root.findall("source/host")[0].attrib) + self.assertEqual( + root.findall("source/host")[1].attrib["name"], "my.ceph.monitor" + ) + self.assertEqual(root.findall("source/host")[1].attrib["port"], "69") + self.assertEqual(root.find("source/auth").attrib["type"], "ceph") + self.assertEqual(root.find("source/auth").attrib["username"], "admin") + self.assertEqual(root.find("source/auth/secret").attrib["uuid"], "someuuid") def test_pool_with_netfs(self): - ''' + """ Test virt._gen_pool_xml() with a netfs source - ''' - xml_data = virt._gen_pool_xml('pool', - 'netfs', - target='/path/to/target', - permissions={ - 'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel' - }, - source_devices=[{'path': '/dev/sda'}], - source_hosts=['nfs.host'], - source_name='srcname', - source_adapter={'type': 'scsi_host', 'name': 'host0'}, - source_dir='/some/dir', - source_format='nfs') + """ + xml_data = virt._gen_pool_xml( + "pool", + "netfs", + target="/path/to/target", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_hosts=["nfs.host"], + source_name="srcname", + source_adapter={"type": "scsi_host", "name": "host0"}, + source_dir="/some/dir", + source_format="nfs", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'netfs') - self.assertEqual(root.find('target/path').text, '/path/to/target') - self.assertEqual(root.find('target/permissions/mode').text, '0770') - self.assertEqual(root.find('target/permissions/owner').text, '1000') - self.assertEqual(root.find('target/permissions/group').text, '100') - self.assertEqual(root.find('target/permissions/label').text, 'seclabel') - self.assertEqual(root.find('source/device'), None) - self.assertEqual(root.find('source/name'), None) - self.assertEqual(root.find('source/adapter'), None) - self.assertEqual(root.find('source/dir').attrib['path'], '/some/dir') - self.assertEqual(root.find('source/format').attrib['type'], 'nfs') - self.assertEqual(root.find('source/host').attrib['name'], 'nfs.host') - self.assertEqual(root.find('source/auth'), None) + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "netfs") + self.assertEqual(root.find("target/path").text, "/path/to/target") + self.assertEqual(root.find("target/permissions/mode").text, "0770") + self.assertEqual(root.find("target/permissions/owner").text, "1000") + self.assertEqual(root.find("target/permissions/group").text, "100") + self.assertEqual(root.find("target/permissions/label").text, "seclabel") + self.assertEqual(root.find("source/device"), None) + self.assertEqual(root.find("source/name"), None) + self.assertEqual(root.find("source/adapter"), None) + self.assertEqual(root.find("source/dir").attrib["path"], "/some/dir") + self.assertEqual(root.find("source/format").attrib["type"], "nfs") + self.assertEqual(root.find("source/host").attrib["name"], "nfs.host") + self.assertEqual(root.find("source/auth"), None) def test_pool_with_iscsi_direct(self): - ''' + """ Test virt._gen_pool_xml() with a iscsi-direct source - ''' - xml_data = virt._gen_pool_xml('pool', - 'iscsi-direct', - source_hosts=['iscsi.example.com'], - source_devices=[{'path': 'iqn.2013-06.com.example:iscsi-pool'}], - source_initiator='iqn.2013-06.com.example:iscsi-initiator') + """ + xml_data = virt._gen_pool_xml( + "pool", + "iscsi-direct", + source_hosts=["iscsi.example.com"], + source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}], + source_initiator="iqn.2013-06.com.example:iscsi-initiator", + ) root = ET.fromstring(xml_data) - self.assertEqual(root.find('name').text, 'pool') - self.assertEqual(root.attrib['type'], 'iscsi-direct') - self.assertEqual(root.find('target'), None) - self.assertEqual(root.find('source/device').attrib['path'], 'iqn.2013-06.com.example:iscsi-pool') - self.assertEqual(root.findall('source/host')[0].attrib['name'], 'iscsi.example.com') - self.assertEqual(root.find('source/initiator/iqn').attrib['name'], 'iqn.2013-06.com.example:iscsi-initiator') + self.assertEqual(root.find("name").text, "pool") + self.assertEqual(root.attrib["type"], "iscsi-direct") + self.assertEqual(root.find("target"), None) + self.assertEqual( + root.find("source/device").attrib["path"], + "iqn.2013-06.com.example:iscsi-pool", + ) + self.assertEqual( + root.findall("source/host")[0].attrib["name"], "iscsi.example.com" + ) + self.assertEqual( + root.find("source/initiator/iqn").attrib["name"], + "iqn.2013-06.com.example:iscsi-initiator", + ) def test_pool_define(self): - ''' + """ Test virt.pool_define() - ''' + """ mock_pool = MagicMock() mock_secret = MagicMock() mock_secret_define = MagicMock(return_value=mock_secret) @@ -2595,22 +3930,29 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolCreateXML = MagicMock(return_value=mock_pool) self.mock_conn.storagePoolDefineXML = MagicMock(return_value=mock_pool) - mocks = [mock_pool, mock_secret, mock_secret_define, self.mock_conn.storagePoolCreateXML, - self.mock_conn.secretDefineXML, self.mock_conn.storagePoolDefineXML] + mocks = [ + mock_pool, + mock_secret, + mock_secret_define, + self.mock_conn.storagePoolCreateXML, + self.mock_conn.secretDefineXML, + self.mock_conn.storagePoolDefineXML, + ] # Test case with already defined secret and permanent pool - self.assertTrue(virt.pool_define('default', - 'rbd', - source_hosts=['one.example.com', 'two.example.com'], - source_name='rbdvol', - source_auth={ - 'type': 'ceph', - 'username': 'admin', - 'secret': { - 'type': 'uuid', - 'value': 'someuuid' - } - })) + self.assertTrue( + virt.pool_define( + "default", + "rbd", + source_hosts=["one.example.com", "two.example.com"], + source_name="rbdvol", + source_auth={ + "type": "ceph", + "username": "admin", + "secret": {"type": "uuid", "value": "someuuid"}, + }, + ) + ) self.mock_conn.storagePoolDefineXML.assert_called_once() self.mock_conn.storagePoolCreateXML.assert_not_called() mock_pool.create.assert_called_once() @@ -2619,87 +3961,99 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): # Test case with Ceph secret to be defined and transient pool for mock in mocks: mock.reset_mock() - self.assertTrue(virt.pool_define('default', - 'rbd', - transient=True, - source_hosts=['one.example.com', 'two.example.com'], - source_name='rbdvol', - source_auth={ - 'username': 'admin', - 'password': 'c2VjcmV0' - })) + self.assertTrue( + virt.pool_define( + "default", + "rbd", + transient=True, + source_hosts=["one.example.com", "two.example.com"], + source_name="rbdvol", + source_auth={"username": "admin", "password": "c2VjcmV0"}, + ) + ) self.mock_conn.storagePoolDefineXML.assert_not_called() pool_xml = self.mock_conn.storagePoolCreateXML.call_args[0][0] root = ET.fromstring(pool_xml) - self.assertEqual(root.find('source/auth').attrib['type'], 'ceph') - self.assertEqual(root.find('source/auth').attrib['username'], 'admin') - self.assertEqual(root.find('source/auth/secret').attrib['usage'], 'pool_default') + self.assertEqual(root.find("source/auth").attrib["type"], "ceph") + self.assertEqual(root.find("source/auth").attrib["username"], "admin") + self.assertEqual( + root.find("source/auth/secret").attrib["usage"], "pool_default" + ) mock_pool.create.assert_not_called() - mock_secret.setValue.assert_called_once_with(b'secret') + mock_secret.setValue.assert_called_once_with(b"secret") secret_xml = mock_secret_define.call_args[0][0] root = ET.fromstring(secret_xml) - self.assertEqual(root.find('usage/name').text, 'pool_default') - self.assertEqual(root.find('usage').attrib['type'], 'ceph') - self.assertEqual(root.attrib['private'], 'yes') - self.assertEqual(root.find('description').text, 'Passphrase for default pool created by Salt') + self.assertEqual(root.find("usage/name").text, "pool_default") + self.assertEqual(root.find("usage").attrib["type"], "ceph") + self.assertEqual(root.attrib["private"], "yes") + self.assertEqual( + root.find("description").text, "Passphrase for default pool created by Salt" + ) # Test case with iscsi secret not starting for mock in mocks: mock.reset_mock() - self.assertTrue(virt.pool_define('default', - 'iscsi', - target='/dev/disk/by-path', - source_hosts=['iscsi.example.com'], - source_devices=[{'path': 'iqn.2013-06.com.example:iscsi-pool'}], - source_auth={ - 'username': 'admin', - 'password': 'secret' - }, - start=False)) + self.assertTrue( + virt.pool_define( + "default", + "iscsi", + target="/dev/disk/by-path", + source_hosts=["iscsi.example.com"], + source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}], + source_auth={"username": "admin", "password": "secret"}, + start=False, + ) + ) self.mock_conn.storagePoolCreateXML.assert_not_called() pool_xml = self.mock_conn.storagePoolDefineXML.call_args[0][0] root = ET.fromstring(pool_xml) - self.assertEqual(root.find('source/auth').attrib['type'], 'chap') - self.assertEqual(root.find('source/auth').attrib['username'], 'admin') - self.assertEqual(root.find('source/auth/secret').attrib['usage'], 'pool_default') + self.assertEqual(root.find("source/auth").attrib["type"], "chap") + self.assertEqual(root.find("source/auth").attrib["username"], "admin") + self.assertEqual( + root.find("source/auth/secret").attrib["usage"], "pool_default" + ) mock_pool.create.assert_not_called() - mock_secret.setValue.assert_called_once_with('secret') + mock_secret.setValue.assert_called_once_with("secret") secret_xml = mock_secret_define.call_args[0][0] root = ET.fromstring(secret_xml) - self.assertEqual(root.find('usage/target').text, 'pool_default') - self.assertEqual(root.find('usage').attrib['type'], 'iscsi') - self.assertEqual(root.attrib['private'], 'yes') - self.assertEqual(root.find('description').text, 'Passphrase for default pool created by Salt') + self.assertEqual(root.find("usage/target").text, "pool_default") + self.assertEqual(root.find("usage").attrib["type"], "iscsi") + self.assertEqual(root.attrib["private"], "yes") + self.assertEqual( + root.find("description").text, "Passphrase for default pool created by Salt" + ) def test_list_pools(self): - ''' + """ Test virt.list_pools() - ''' - names = ['pool1', 'default', 'pool2'] + """ + names = ["pool1", "default", "pool2"] pool_mocks = [MagicMock(), MagicMock(), MagicMock()] for i, value in enumerate(names): pool_mocks[i].name.return_value = value - self.mock_conn.listAllStoragePools.return_value = pool_mocks # pylint: disable=no-member + self.mock_conn.listAllStoragePools.return_value = ( + pool_mocks # pylint: disable=no-member + ) actual = virt.list_pools() self.assertEqual(names, actual) def test_pool_info(self): - ''' + """ Test virt.pool_info() - ''' + """ # pylint: disable=no-member pool_mock = MagicMock() - pool_mock.name.return_value = 'foo' - pool_mock.UUIDString.return_value = 'some-uuid' + pool_mock.name.return_value = "foo" + pool_mock.UUIDString.return_value = "some-uuid" pool_mock.info.return_value = [0, 1234, 5678, 123] pool_mock.autostart.return_value = True pool_mock.isPersistent.return_value = True - pool_mock.XMLDesc.return_value = '''<pool type='dir'> + pool_mock.XMLDesc.return_value = """<pool type='dir'> <name>default</name> <uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid> <capacity unit='bytes'>854374301696</capacity> @@ -2715,34 +4069,40 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <group>0</group> </permissions> </target> -</pool>''' +</pool>""" self.mock_conn.listAllStoragePools.return_value = [pool_mock] # pylint: enable=no-member - pool = virt.pool_info('foo') - self.assertEqual({'foo': { - 'uuid': 'some-uuid', - 'state': 'inactive', - 'capacity': 1234, - 'allocation': 5678, - 'free': 123, - 'autostart': True, - 'persistent': True, - 'type': 'dir', - 'target_path': '/srv/vms'}}, pool) + pool = virt.pool_info("foo") + self.assertEqual( + { + "foo": { + "uuid": "some-uuid", + "state": "inactive", + "capacity": 1234, + "allocation": 5678, + "free": 123, + "autostart": True, + "persistent": True, + "type": "dir", + "target_path": "/srv/vms", + } + }, + pool, + ) def test_pool_info_notarget(self): - ''' + """ Test virt.pool_info() - ''' + """ # pylint: disable=no-member pool_mock = MagicMock() - pool_mock.name.return_value = 'ceph' - pool_mock.UUIDString.return_value = 'some-uuid' + pool_mock.name.return_value = "ceph" + pool_mock.UUIDString.return_value = "some-uuid" pool_mock.info.return_value = [0, 0, 0, 0] pool_mock.autostart.return_value = True pool_mock.isPersistent.return_value = True - pool_mock.XMLDesc.return_value = '''<pool type='rbd'> + pool_mock.XMLDesc.return_value = """<pool type='rbd'> <name>ceph</name> <uuid>some-uuid</uuid> <capacity unit='bytes'>0</capacity> @@ -2756,46 +4116,52 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <secret uuid='2ec115d7-3a88-3ceb-bc12-0ac909a6fd87'/> </auth> </source> -</pool>''' +</pool>""" self.mock_conn.listAllStoragePools.return_value = [pool_mock] # pylint: enable=no-member - pool = virt.pool_info('ceph') - self.assertEqual({'ceph': { - 'uuid': 'some-uuid', - 'state': 'inactive', - 'capacity': 0, - 'allocation': 0, - 'free': 0, - 'autostart': True, - 'persistent': True, - 'type': 'rbd', - 'target_path': None}}, pool) + pool = virt.pool_info("ceph") + self.assertEqual( + { + "ceph": { + "uuid": "some-uuid", + "state": "inactive", + "capacity": 0, + "allocation": 0, + "free": 0, + "autostart": True, + "persistent": True, + "type": "rbd", + "target_path": None, + } + }, + pool, + ) def test_pool_info_notfound(self): - ''' + """ Test virt.pool_info() when the pool can't be found - ''' + """ # pylint: disable=no-member self.mock_conn.listAllStoragePools.return_value = [] # pylint: enable=no-member - pool = virt.pool_info('foo') + pool = virt.pool_info("foo") self.assertEqual({}, pool) def test_pool_info_all(self): - ''' + """ Test virt.pool_info() - ''' + """ # pylint: disable=no-member pool_mocks = [] for i in range(2): pool_mock = MagicMock() - pool_mock.name.return_value = 'pool{0}'.format(i) - pool_mock.UUIDString.return_value = 'some-uuid-{0}'.format(i) + pool_mock.name.return_value = "pool{0}".format(i) + pool_mock.UUIDString.return_value = "some-uuid-{0}".format(i) pool_mock.info.return_value = [0, 1234, 5678, 123] pool_mock.autostart.return_value = True pool_mock.isPersistent.return_value = True - pool_mock.XMLDesc.return_value = '''<pool type='dir'> + pool_mock.XMLDesc.return_value = """<pool type='dir'> <name>default</name> <uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid> <capacity unit='bytes'>854374301696</capacity> @@ -2811,95 +4177,143 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <group>0</group> </permissions> </target> -</pool>''' +</pool>""" pool_mocks.append(pool_mock) self.mock_conn.listAllStoragePools.return_value = pool_mocks # pylint: enable=no-member pool = virt.pool_info() - self.assertEqual({ - 'pool0': + self.assertEqual( { - 'uuid': 'some-uuid-0', - 'state': 'inactive', - 'capacity': 1234, - 'allocation': 5678, - 'free': 123, - 'autostart': True, - 'persistent': True, - 'type': 'dir', - 'target_path': '/srv/vms' - }, 'pool1': { - 'uuid': 'some-uuid-1', - 'state': 'inactive', - 'capacity': 1234, - 'allocation': 5678, - 'free': 123, - 'autostart': True, - 'persistent': True, - 'type': 'dir', - 'target_path': '/srv/vms' - } - }, pool) + "pool0": { + "uuid": "some-uuid-0", + "state": "inactive", + "capacity": 1234, + "allocation": 5678, + "free": 123, + "autostart": True, + "persistent": True, + "type": "dir", + "target_path": "/srv/vms", + }, + "pool1": { + "uuid": "some-uuid-1", + "state": "inactive", + "capacity": 1234, + "allocation": 5678, + "free": 123, + "autostart": True, + "persistent": True, + "type": "dir", + "target_path": "/srv/vms", + }, + }, + pool, + ) def test_pool_get_xml(self): - ''' + """ Test virt.pool_get_xml - ''' + """ pool_mock = MagicMock() - pool_mock.XMLDesc.return_value = '<pool>Raw XML</pool>' + pool_mock.XMLDesc.return_value = "<pool>Raw XML</pool>" self.mock_conn.storagePoolLookupByName.return_value = pool_mock - self.assertEqual('<pool>Raw XML</pool>', virt.pool_get_xml('default')) + self.assertEqual("<pool>Raw XML</pool>", virt.pool_get_xml("default")) def test_pool_list_volumes(self): - ''' + """ Test virt.pool_list_volumes - ''' - names = ['volume1', 'volume2'] + """ + names = ["volume1", "volume2"] mock_pool = MagicMock() # pylint: disable=no-member mock_pool.listVolumes.return_value = names self.mock_conn.storagePoolLookupByName.return_value = mock_pool # pylint: enable=no-member - self.assertEqual(names, virt.pool_list_volumes('default')) + self.assertEqual(names, virt.pool_list_volumes("default")) - @patch('salt.modules.virt._is_kvm_hyper', return_value=True) - @patch('salt.modules.virt._is_xen_hyper', return_value=False) - def test_get_hypervisor(self, isxen_mock, iskvm_mock): - ''' + @patch("salt.modules.virt._is_bhyve_hyper", return_value=False) + @patch("salt.modules.virt._is_kvm_hyper", return_value=True) + @patch("salt.modules.virt._is_xen_hyper", return_value=False) + def test_get_hypervisor(self, isxen_mock, iskvm_mock, is_bhyve_mock): + """ test the virt.get_hypervisor() function - ''' - self.assertEqual('kvm', virt.get_hypervisor()) + """ + self.assertEqual("kvm", virt.get_hypervisor()) iskvm_mock.return_value = False self.assertIsNone(virt.get_hypervisor()) + is_bhyve_mock.return_value = False + self.assertIsNone(virt.get_hypervisor()) + isxen_mock.return_value = True - self.assertEqual('xen', virt.get_hypervisor()) + self.assertEqual("xen", virt.get_hypervisor()) def test_pool_delete(self): - ''' + """ Test virt.pool_delete function - ''' + """ mock_pool = MagicMock() mock_pool.delete = MagicMock(return_value=0) + mock_pool.XMLDesc.return_value = "<pool type='dir'/>" self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool) - res = virt.pool_delete('test-pool') + res = virt.pool_delete("test-pool") self.assertTrue(res) - self.mock_conn.storagePoolLookupByName.assert_called_once_with('test-pool') + self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-pool") # Shouldn't be called with another parameter so far since those are not implemented # and thus throwing exceptions. - mock_pool.delete.assert_called_once_with(self.mock_libvirt.VIR_STORAGE_POOL_DELETE_NORMAL) + mock_pool.delete.assert_called_once_with( + self.mock_libvirt.VIR_STORAGE_POOL_DELETE_NORMAL + ) + + def test_pool_delete_secret(self): + """ + Test virt.pool_delete function where the pool has a secret + """ + mock_pool = MagicMock() + mock_pool.delete = MagicMock(return_value=0) + mock_pool.XMLDesc.return_value = """ + <pool type='rbd'> + <name>test-ses</name> + <source> + <host name='myhost'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret usage='pool_test-ses'/> + </auth> + </source> + </pool> + """ + self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool) + mock_undefine = MagicMock(return_value=0) + self.mock_conn.secretLookupByUsage.return_value.undefine = mock_undefine + + res = virt.pool_delete("test-ses") + self.assertTrue(res) + + self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-ses") + + # Shouldn't be called with another parameter so far since those are not implemented + # and thus throwing exceptions. + mock_pool.delete.assert_called_once_with( + self.mock_libvirt.VIR_STORAGE_POOL_DELETE_NORMAL + ) + + self.mock_conn.secretLookupByUsage.assert_called_once_with( + self.mock_libvirt.VIR_SECRET_USAGE_TYPE_CEPH, "pool_test-ses" + ) + mock_undefine.assert_called_once() def test_full_info(self): - ''' + """ Test virt.full_info - ''' - xml = '''<domain type='kvm' id='7'> + """ + xml = """<domain type='kvm' id='7'> <uuid>28deee33-4859-4f23-891c-ee239cffec94</uuid> <name>test-vm</name> <on_poweroff>destroy</on_poweroff> @@ -2928,10 +4342,10 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): </graphics> </devices> </domain> - ''' + """ self.set_mock_vm("test-vm", xml) - qemu_infos = '''[{ + qemu_infos = """[{ "virtual-size": 25769803776, "filename": "/disks/test.qcow2", "cluster-size": 65536, @@ -2966,69 +4380,79 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): } }, "dirty-flag": false - }]''' + }]""" - self.mock_popen.communicate.return_value = [qemu_infos] # pylint: disable=no-member + self.mock_popen.communicate.return_value = [ + qemu_infos + ] # pylint: disable=no-member - self.mock_conn.getInfo = MagicMock(return_value=['x86_64', 4096, 8, 2712, 1, 2, 4, 2]) + self.mock_conn.getInfo = MagicMock( + return_value=["x86_64", 4096, 8, 2712, 1, 2, 4, 2] + ) actual = virt.full_info() # Check that qemu-img was called with the proper parameters - qemu_img_call = [call for call in self.mock_subprocess.Popen.call_args_list if 'qemu-img' in call[0][0]][0] - self.assertIn('info', qemu_img_call[0][0]) - self.assertIn('-U', qemu_img_call[0][0]) + qemu_img_call = [ + call + for call in self.mock_subprocess.Popen.call_args_list + if "qemu-img" in call[0][0] + ][0] + self.assertIn("info", qemu_img_call[0][0]) + self.assertIn("-U", qemu_img_call[0][0]) # Test the hypervisor infos - self.assertEqual(2816, actual['freemem']) - self.assertEqual(6, actual['freecpu']) - self.assertEqual(4, actual['node_info']['cpucores']) - self.assertEqual(2712, actual['node_info']['cpumhz']) - self.assertEqual('x86_64', actual['node_info']['cpumodel']) - self.assertEqual(8, actual['node_info']['cpus']) - self.assertEqual(2, actual['node_info']['cputhreads']) - self.assertEqual(1, actual['node_info']['numanodes']) - self.assertEqual(4096, actual['node_info']['phymemory']) - self.assertEqual(2, actual['node_info']['sockets']) + self.assertEqual(2816, actual["freemem"]) + self.assertEqual(6, actual["freecpu"]) + self.assertEqual(4, actual["node_info"]["cpucores"]) + self.assertEqual(2712, actual["node_info"]["cpumhz"]) + self.assertEqual("x86_64", actual["node_info"]["cpumodel"]) + self.assertEqual(8, actual["node_info"]["cpus"]) + self.assertEqual(2, actual["node_info"]["cputhreads"]) + self.assertEqual(1, actual["node_info"]["numanodes"]) + self.assertEqual(4096, actual["node_info"]["phymemory"]) + self.assertEqual(2, actual["node_info"]["sockets"]) # Test the vm_info output: - self.assertEqual(2, actual['vm_info']['test-vm']['cpu']) - self.assertEqual(1234, actual['vm_info']['test-vm']['cputime']) - self.assertEqual(1024 * 1024, actual['vm_info']['test-vm']['mem']) - self.assertEqual(2048 * 1024, actual['vm_info']['test-vm']['maxMem']) - self.assertEqual('shutdown', actual['vm_info']['test-vm']['state']) - self.assertEqual('28deee33-4859-4f23-891c-ee239cffec94', actual['vm_info']['test-vm']['uuid']) - self.assertEqual('destroy', actual['vm_info']['test-vm']['on_crash']) - self.assertEqual('restart', actual['vm_info']['test-vm']['on_reboot']) - self.assertEqual('destroy', actual['vm_info']['test-vm']['on_poweroff']) + self.assertEqual(2, actual["vm_info"]["test-vm"]["cpu"]) + self.assertEqual(1234, actual["vm_info"]["test-vm"]["cputime"]) + self.assertEqual(1024 * 1024, actual["vm_info"]["test-vm"]["mem"]) + self.assertEqual(2048 * 1024, actual["vm_info"]["test-vm"]["maxMem"]) + self.assertEqual("shutdown", actual["vm_info"]["test-vm"]["state"]) + self.assertEqual( + "28deee33-4859-4f23-891c-ee239cffec94", actual["vm_info"]["test-vm"]["uuid"] + ) + self.assertEqual("destroy", actual["vm_info"]["test-vm"]["on_crash"]) + self.assertEqual("restart", actual["vm_info"]["test-vm"]["on_reboot"]) + self.assertEqual("destroy", actual["vm_info"]["test-vm"]["on_poweroff"]) # Test the nics - nic = actual['vm_info']['test-vm']['nics']['ac:de:48:b6:8b:59'] - self.assertEqual('bridge', nic['type']) - self.assertEqual('ac:de:48:b6:8b:59', nic['mac']) + nic = actual["vm_info"]["test-vm"]["nics"]["ac:de:48:b6:8b:59"] + self.assertEqual("bridge", nic["type"]) + self.assertEqual("ac:de:48:b6:8b:59", nic["mac"]) # Test the disks - disks = actual['vm_info']['test-vm']['disks'] - disk = disks.get('vda') - self.assertEqual('/disks/test.qcow2', disk['file']) - self.assertEqual('disk', disk['type']) - self.assertEqual('/disks/mybacking.qcow2', disk['backing file']['file']) - cdrom = disks.get('hda') - self.assertEqual('/disks/test-cdrom.iso', cdrom['file']) - self.assertEqual('cdrom', cdrom['type']) - self.assertFalse('backing file' in cdrom.keys()) + disks = actual["vm_info"]["test-vm"]["disks"] + disk = disks.get("vda") + self.assertEqual("/disks/test.qcow2", disk["file"]) + self.assertEqual("disk", disk["type"]) + self.assertEqual("/disks/mybacking.qcow2", disk["backing file"]["file"]) + cdrom = disks.get("hda") + self.assertEqual("/disks/test-cdrom.iso", cdrom["file"]) + self.assertEqual("cdrom", cdrom["type"]) + self.assertFalse("backing file" in cdrom.keys()) # Test the graphics - graphics = actual['vm_info']['test-vm']['graphics'] - self.assertEqual('vnc', graphics['type']) - self.assertEqual('5900', graphics['port']) - self.assertEqual('0.0.0.0', graphics['listen']) + graphics = actual["vm_info"]["test-vm"]["graphics"] + self.assertEqual("vnc", graphics["type"]) + self.assertEqual("5900", graphics["port"]) + self.assertEqual("0.0.0.0", graphics["listen"]) def test_pool_update(self): - ''' + """ Test the pool_update function - ''' - current_xml = '''<pool type='dir'> + """ + current_xml = """<pool type='dir'> <name>default</name> <uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid> <capacity unit='bytes'>1999421108224</capacity> @@ -3044,29 +4468,31 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <group>100</group> </permissions> </target> - </pool>''' - - expected_xml = '<pool type="netfs">' \ - '<name>default</name>' \ - '<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>' \ - '<capacity unit="bytes">1999421108224</capacity>' \ - '<allocation unit="bytes">713207042048</allocation>' \ - '<available unit="bytes">1286214066176</available>' \ - '<target>' \ - '<path>/mnt/cifs</path>' \ - '<permissions>' \ - '<mode>0774</mode>' \ - '<owner>1234</owner>' \ - '<group>123</group>' \ - '</permissions>' \ - '</target>' \ - '<source>' \ - '<dir path="samba_share" />' \ - '<host name="one.example.com" />' \ - '<host name="two.example.com" />' \ - '<format type="cifs" />' \ - '</source>' \ - '</pool>' + </pool>""" + + expected_xml = ( + '<pool type="netfs">' + "<name>default</name>" + "<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>" + '<capacity unit="bytes">1999421108224</capacity>' + '<allocation unit="bytes">713207042048</allocation>' + '<available unit="bytes">1286214066176</available>' + "<target>" + "<path>/mnt/cifs</path>" + "<permissions>" + "<mode>0774</mode>" + "<owner>1234</owner>" + "<group>123</group>" + "</permissions>" + "</target>" + "<source>" + '<dir path="samba_share" />' + '<host name="one.example.com" />' + '<host name="two.example.com" />' + '<format type="cifs" />' + "</source>" + "</pool>" + ) mocked_pool = MagicMock() mocked_pool.XMLDesc = MagicMock(return_value=current_xml) @@ -3074,21 +4500,24 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolDefineXML = MagicMock() self.assertTrue( - virt.pool_update('default', - 'netfs', - target='/mnt/cifs', - permissions={'mode': '0774', 'owner': '1234', 'group': '123'}, - source_format='cifs', - source_dir='samba_share', - source_hosts=['one.example.com', 'two.example.com'])) + virt.pool_update( + "default", + "netfs", + target="/mnt/cifs", + permissions={"mode": "0774", "owner": "1234", "group": "123"}, + source_format="cifs", + source_dir="samba_share", + source_hosts=["one.example.com", "two.example.com"], + ) + ) self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml) def test_pool_update_nochange(self): - ''' + """ Test the pool_update function when no change is needed - ''' + """ - current_xml = '''<pool type='dir'> + current_xml = """<pool type='dir'> <name>default</name> <uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid> <capacity unit='bytes'>1999421108224</capacity> @@ -3104,7 +4533,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <group>100</group> </permissions> </target> - </pool>''' + </pool>""" mocked_pool = MagicMock() mocked_pool.XMLDesc = MagicMock(return_value=current_xml) @@ -3112,18 +4541,21 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolDefineXML = MagicMock() self.assertFalse( - virt.pool_update('default', - 'dir', - target='/path/to/pool', - permissions={'mode': '0775', 'owner': '0', 'group': '100'}, - test=True)) + virt.pool_update( + "default", + "dir", + target="/path/to/pool", + permissions={"mode": "0775", "owner": "0", "group": "100"}, + test=True, + ) + ) self.mock_conn.storagePoolDefineXML.assert_not_called() def test_pool_update_password(self): - ''' + """ Test the pool_update function, where the password only is changed - ''' - current_xml = '''<pool type='rbd'> + """ + current_xml = """<pool type='rbd'> <name>default</name> <uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid> <capacity unit='bytes'>1999421108224</capacity> @@ -3137,23 +4569,25 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <secret uuid='14e9a0f1-8fbf-4097-b816-5b094c182212'/> </auth> </source> - </pool>''' - - expected_xml = '<pool type="rbd">' \ - '<name>default</name>' \ - '<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>' \ - '<capacity unit="bytes">1999421108224</capacity>' \ - '<allocation unit="bytes">713207042048</allocation>' \ - '<available unit="bytes">1286214066176</available>' \ - '<source>' \ - '<host name="ses4.tf.local" />' \ - '<host name="ses5.tf.local" />' \ - '<auth type="ceph" username="libvirt">' \ - '<secret uuid="14e9a0f1-8fbf-4097-b816-5b094c182212" />' \ - '</auth>' \ - '<name>iscsi-images</name>' \ - '</source>' \ - '</pool>' + </pool>""" + + expected_xml = ( + '<pool type="rbd">' + "<name>default</name>" + "<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>" + '<capacity unit="bytes">1999421108224</capacity>' + '<allocation unit="bytes">713207042048</allocation>' + '<available unit="bytes">1286214066176</available>' + "<source>" + '<host name="ses4.tf.local" />' + '<host name="ses5.tf.local" />' + '<auth type="ceph" username="libvirt">' + '<secret uuid="14e9a0f1-8fbf-4097-b816-5b094c182212" />' + "</auth>" + "<name>iscsi-images</name>" + "</source>" + "</pool>" + ) mock_secret = MagicMock() self.mock_conn.secretLookupByUUIDString = MagicMock(return_value=mock_secret) @@ -3163,21 +4597,23 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mocked_pool) self.mock_conn.storagePoolDefineXML = MagicMock() - self.assertTrue( - virt.pool_update('default', - 'rbd', - source_name='iscsi-images', - source_hosts=['ses4.tf.local', 'ses5.tf.local'], - source_auth={'username': 'libvirt', - 'password': 'c2VjcmV0'})) - self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml) - mock_secret.setValue.assert_called_once_with(b'secret') + self.assertFalse( + virt.pool_update( + "default", + "rbd", + source_name="iscsi-images", + source_hosts=["ses4.tf.local", "ses5.tf.local"], + source_auth={"username": "libvirt", "password": "c2VjcmV0"}, + ) + ) + self.mock_conn.storagePoolDefineXML.assert_not_called() + mock_secret.setValue.assert_called_once_with(b"secret") def test_pool_update_password_create(self): - ''' + """ Test the pool_update function, where the password only is changed - ''' - current_xml = '''<pool type='rbd'> + """ + current_xml = """<pool type='rbd'> <name>default</name> <uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid> <capacity unit='bytes'>1999421108224</capacity> @@ -3188,23 +4624,25 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <host name='ses4.tf.local'/> <host name='ses5.tf.local'/> </source> - </pool>''' - - expected_xml = '<pool type="rbd">' \ - '<name>default</name>' \ - '<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>' \ - '<capacity unit="bytes">1999421108224</capacity>' \ - '<allocation unit="bytes">713207042048</allocation>' \ - '<available unit="bytes">1286214066176</available>' \ - '<source>' \ - '<host name="ses4.tf.local" />' \ - '<host name="ses5.tf.local" />' \ - '<auth type="ceph" username="libvirt">' \ - '<secret usage="pool_default" />' \ - '</auth>' \ - '<name>iscsi-images</name>' \ - '</source>' \ - '</pool>' + </pool>""" + + expected_xml = ( + '<pool type="rbd">' + "<name>default</name>" + "<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>" + '<capacity unit="bytes">1999421108224</capacity>' + '<allocation unit="bytes">713207042048</allocation>' + '<available unit="bytes">1286214066176</available>' + "<source>" + '<host name="ses4.tf.local" />' + '<host name="ses5.tf.local" />' + '<auth type="ceph" username="libvirt">' + '<secret usage="pool_default" />' + "</auth>" + "<name>iscsi-images</name>" + "</source>" + "</pool>" + ) mock_secret = MagicMock() self.mock_conn.secretDefineXML = MagicMock(return_value=mock_secret) @@ -3215,316 +4653,399 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.storagePoolDefineXML = MagicMock() self.assertTrue( - virt.pool_update('default', - 'rbd', - source_name='iscsi-images', - source_hosts=['ses4.tf.local', 'ses5.tf.local'], - source_auth={'username': 'libvirt', - 'password': 'c2VjcmV0'})) + virt.pool_update( + "default", + "rbd", + source_name="iscsi-images", + source_hosts=["ses4.tf.local", "ses5.tf.local"], + source_auth={"username": "libvirt", "password": "c2VjcmV0"}, + ) + ) self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml) - mock_secret.setValue.assert_called_once_with(b'secret') + mock_secret.setValue.assert_called_once_with(b"secret") def test_volume_infos(self): - ''' + """ Test virt.volume_infos - ''' + """ vms_disks = [ - ''' + """ <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/path/to/vol0.qcow2'/> <target dev='vda' bus='virtio'/> </disk> - ''', - ''' + """, + """ <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/path/to/vol3.qcow2'/> <target dev='vda' bus='virtio'/> </disk> - ''', - ''' + """, + """ <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/path/to/vol2.qcow2'/> <target dev='vda' bus='virtio'/> </disk> - ''' + """, ] mock_vms = [] for idx, disk in enumerate(vms_disks): vm = MagicMock() # pylint: disable=no-member - vm.name.return_value = 'vm{0}'.format(idx) - vm.XMLDesc.return_value = ''' + vm.name.return_value = "vm{0}".format(idx) + vm.XMLDesc.return_value = """ <domain type='kvm' id='1'> <name>vm{0}</name> <devices>{1}</devices> </domain> - '''.format(idx, disk) + """.format( + idx, disk + ) # pylint: enable=no-member mock_vms.append(vm) mock_pool_data = [ { - 'name': 'pool0', - 'state': self.mock_libvirt.VIR_STORAGE_POOL_RUNNING, - 'volumes': [ + "name": "pool0", + "state": self.mock_libvirt.VIR_STORAGE_POOL_RUNNING, + "volumes": [ { - 'key': '/key/of/vol0', - 'name': 'vol0', - 'path': '/path/to/vol0.qcow2', - 'info': [0, 123456789, 123456], - 'backingStore': None + "key": "/key/of/vol0", + "name": "vol0", + "path": "/path/to/vol0.qcow2", + "info": [0, 123456789, 123456], + "backingStore": None, } - ] + ], }, { - 'name': 'pool1', - 'state': self.mock_libvirt.VIR_STORAGE_POOL_RUNNING, - 'volumes': [ + "name": "pool1", + "state": self.mock_libvirt.VIR_STORAGE_POOL_RUNNING, + "volumes": [ { - 'key': '/key/of/vol0bad', - 'name': 'vol0bad', - 'path': '/path/to/vol0bad.qcow2', - 'info': None, - 'backingStore': None + "key": "/key/of/vol0bad", + "name": "vol0bad", + "path": "/path/to/vol0bad.qcow2", + "info": None, + "backingStore": None, }, { - 'key': '/key/of/vol1', - 'name': 'vol1', - 'path': '/path/to/vol1.qcow2', - 'info': [0, 12345, 1234], - 'backingStore': None + "key": "/key/of/vol1", + "name": "vol1", + "path": "/path/to/vol1.qcow2", + "info": [0, 12345, 1234], + "backingStore": None, }, { - 'key': '/key/of/vol2', - 'name': 'vol2', - 'path': '/path/to/vol2.qcow2', - 'info': [0, 12345, 1234], - 'backingStore': '/path/to/vol0.qcow2' + "key": "/key/of/vol2", + "name": "vol2", + "path": "/path/to/vol2.qcow2", + "info": [0, 12345, 1234], + "backingStore": "/path/to/vol0.qcow2", }, ], - } + }, ] mock_pools = [] for pool_data in mock_pool_data: mock_pool = MagicMock() - mock_pool.name.return_value = pool_data['name'] # pylint: disable=no-member - mock_pool.info.return_value = [pool_data['state']] + mock_pool.name.return_value = pool_data["name"] # pylint: disable=no-member + mock_pool.info.return_value = [pool_data["state"]] mock_volumes = [] - for vol_data in pool_data['volumes']: + for vol_data in pool_data["volumes"]: mock_volume = MagicMock() # pylint: disable=no-member - mock_volume.name.return_value = vol_data['name'] - mock_volume.key.return_value = vol_data['key'] - mock_volume.path.return_value = '/path/to/{0}.qcow2'.format(vol_data['name']) - if vol_data['info']: - mock_volume.info.return_value = vol_data['info'] - backing_store = ''' + mock_volume.name.return_value = vol_data["name"] + mock_volume.key.return_value = vol_data["key"] + mock_volume.path.return_value = "/path/to/{0}.qcow2".format( + vol_data["name"] + ) + if vol_data["info"]: + mock_volume.info.return_value = vol_data["info"] + backing_store = ( + """ <backingStore> - <format>qcow2</format> + <format type="qcow2"/> <path>{0}</path> </backingStore> - '''.format(vol_data['backingStore']) if vol_data['backingStore'] else '<backingStore/>' - mock_volume.XMLDesc.return_value = ''' + """.format( + vol_data["backingStore"] + ) + if vol_data["backingStore"] + else "<backingStore/>" + ) + mock_volume.XMLDesc.return_value = """ <volume type='file'> <name>{0}</name> <target> - <format>qcow2</format> + <format type="qcow2"/> <path>/path/to/{0}.qcow2</path> </target> {1} </volume> - '''.format(vol_data['name'], backing_store) + """.format( + vol_data["name"], backing_store + ) else: - mock_volume.info.side_effect = self.mock_libvirt.libvirtError('No such volume') - mock_volume.XMLDesc.side_effect = self.mock_libvirt.libvirtError('No such volume') + mock_volume.info.side_effect = self.mock_libvirt.libvirtError( + "No such volume" + ) + mock_volume.XMLDesc.side_effect = self.mock_libvirt.libvirtError( + "No such volume" + ) mock_volumes.append(mock_volume) # pylint: enable=no-member - mock_pool.listAllVolumes.return_value = mock_volumes # pylint: disable=no-member + mock_pool.listAllVolumes.return_value = ( + mock_volumes # pylint: disable=no-member + ) mock_pools.append(mock_pool) inactive_pool = MagicMock() - inactive_pool.name.return_value = 'pool2' + inactive_pool.name.return_value = "pool2" inactive_pool.info.return_value = [self.mock_libvirt.VIR_STORAGE_POOL_INACTIVE] - inactive_pool.listAllVolumes.side_effect = self.mock_libvirt.libvirtError('pool is inactive') + inactive_pool.listAllVolumes.side_effect = self.mock_libvirt.libvirtError( + "pool is inactive" + ) mock_pools.append(inactive_pool) - self.mock_conn.listAllStoragePools.return_value = mock_pools # pylint: disable=no-member + self.mock_conn.listAllStoragePools.return_value = ( + mock_pools # pylint: disable=no-member + ) - with patch('salt.modules.virt._get_domain', MagicMock(return_value=mock_vms)): - actual = virt.volume_infos('pool0', 'vol0') + with patch("salt.modules.virt._get_domain", MagicMock(return_value=mock_vms)): + actual = virt.volume_infos("pool0", "vol0") self.assertEqual(1, len(actual.keys())) - self.assertEqual(1, len(actual['pool0'].keys())) - self.assertEqual(['vm0', 'vm2'], sorted(actual['pool0']['vol0']['used_by'])) - self.assertEqual('/path/to/vol0.qcow2', actual['pool0']['vol0']['path']) - self.assertEqual('file', actual['pool0']['vol0']['type']) - self.assertEqual('/key/of/vol0', actual['pool0']['vol0']['key']) - self.assertEqual(123456789, actual['pool0']['vol0']['capacity']) - self.assertEqual(123456, actual['pool0']['vol0']['allocation']) - - self.assertEqual(virt.volume_infos('pool1', None), { - 'pool1': { - 'vol1': { - 'type': 'file', - 'key': '/key/of/vol1', - 'path': '/path/to/vol1.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], - }, - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': ['vm2'], + self.assertEqual(1, len(actual["pool0"].keys())) + self.assertEqual(["vm0", "vm2"], sorted(actual["pool0"]["vol0"]["used_by"])) + self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"]) + self.assertEqual("file", actual["pool0"]["vol0"]["type"]) + self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"]) + self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"]) + self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"]) + + self.assertEqual( + virt.volume_infos("pool1", None), + { + "pool1": { + "vol1": { + "type": "file", + "key": "/key/of/vol1", + "path": "/path/to/vol1.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": None, + "format": "qcow2", + }, + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": ["vm2"], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + }, } - } - }) - - self.assertEqual(virt.volume_infos(None, 'vol2'), { - 'pool1': { - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': ['vm2'], + }, + ) + + self.assertEqual( + virt.volume_infos(None, "vol2"), + { + "pool1": { + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": ["vm2"], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + } } - } - }) + }, + ) # Single VM test - with patch('salt.modules.virt._get_domain', MagicMock(return_value=mock_vms[0])): - actual = virt.volume_infos('pool0', 'vol0') + with patch( + "salt.modules.virt._get_domain", MagicMock(return_value=mock_vms[0]) + ): + actual = virt.volume_infos("pool0", "vol0") self.assertEqual(1, len(actual.keys())) - self.assertEqual(1, len(actual['pool0'].keys())) - self.assertEqual(['vm0'], sorted(actual['pool0']['vol0']['used_by'])) - self.assertEqual('/path/to/vol0.qcow2', actual['pool0']['vol0']['path']) - self.assertEqual('file', actual['pool0']['vol0']['type']) - self.assertEqual('/key/of/vol0', actual['pool0']['vol0']['key']) - self.assertEqual(123456789, actual['pool0']['vol0']['capacity']) - self.assertEqual(123456, actual['pool0']['vol0']['allocation']) - - self.assertEqual(virt.volume_infos('pool1', None), { - 'pool1': { - 'vol1': { - 'type': 'file', - 'key': '/key/of/vol1', - 'path': '/path/to/vol1.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], - }, - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], + self.assertEqual(1, len(actual["pool0"].keys())) + self.assertEqual(["vm0"], sorted(actual["pool0"]["vol0"]["used_by"])) + self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"]) + self.assertEqual("file", actual["pool0"]["vol0"]["type"]) + self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"]) + self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"]) + self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"]) + + self.assertEqual( + virt.volume_infos("pool1", None), + { + "pool1": { + "vol1": { + "type": "file", + "key": "/key/of/vol1", + "path": "/path/to/vol1.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": None, + "format": "qcow2", + }, + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + }, } - } - }) - - self.assertEqual(virt.volume_infos(None, 'vol2'), { - 'pool1': { - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], + }, + ) + + self.assertEqual( + virt.volume_infos(None, "vol2"), + { + "pool1": { + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + } } - } - }) + }, + ) # No VM test - with patch('salt.modules.virt._get_domain', MagicMock(side_effect=CommandExecutionError('no VM'))): - actual = virt.volume_infos('pool0', 'vol0') + with patch( + "salt.modules.virt._get_domain", + MagicMock(side_effect=CommandExecutionError("no VM")), + ): + actual = virt.volume_infos("pool0", "vol0") self.assertEqual(1, len(actual.keys())) - self.assertEqual(1, len(actual['pool0'].keys())) - self.assertEqual([], sorted(actual['pool0']['vol0']['used_by'])) - self.assertEqual('/path/to/vol0.qcow2', actual['pool0']['vol0']['path']) - self.assertEqual('file', actual['pool0']['vol0']['type']) - self.assertEqual('/key/of/vol0', actual['pool0']['vol0']['key']) - self.assertEqual(123456789, actual['pool0']['vol0']['capacity']) - self.assertEqual(123456, actual['pool0']['vol0']['allocation']) - - self.assertEqual(virt.volume_infos('pool1', None), { - 'pool1': { - 'vol1': { - 'type': 'file', - 'key': '/key/of/vol1', - 'path': '/path/to/vol1.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], - }, - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], + self.assertEqual(1, len(actual["pool0"].keys())) + self.assertEqual([], sorted(actual["pool0"]["vol0"]["used_by"])) + self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"]) + self.assertEqual("file", actual["pool0"]["vol0"]["type"]) + self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"]) + self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"]) + self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"]) + + self.assertEqual( + virt.volume_infos("pool1", None), + { + "pool1": { + "vol1": { + "type": "file", + "key": "/key/of/vol1", + "path": "/path/to/vol1.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": None, + "format": "qcow2", + }, + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + }, } - } - }) - - self.assertEqual(virt.volume_infos(None, 'vol2'), { - 'pool1': { - 'vol2': { - 'type': 'file', - 'key': '/key/of/vol2', - 'path': '/path/to/vol2.qcow2', - 'capacity': 12345, - 'allocation': 1234, - 'used_by': [], + }, + ) + + self.assertEqual( + virt.volume_infos(None, "vol2"), + { + "pool1": { + "vol2": { + "type": "file", + "key": "/key/of/vol2", + "path": "/path/to/vol2.qcow2", + "capacity": 12345, + "allocation": 1234, + "used_by": [], + "backing_store": { + "path": "/path/to/vol0.qcow2", + "format": "qcow2", + }, + "format": "qcow2", + } } - } - }) + }, + ) def test_volume_delete(self): - ''' + """ Test virt.volume_delete - ''' + """ mock_delete = MagicMock(side_effect=[0, 1]) mock_volume = MagicMock() mock_volume.delete = mock_delete # pylint: disable=no-member mock_pool = MagicMock() # pylint: disable=no-member mock_pool.storageVolLookupByName.side_effect = [ - mock_volume, - mock_volume, - self.mock_libvirt.libvirtError("Missing volume"), - mock_volume, + mock_volume, + mock_volume, + self.mock_libvirt.libvirtError("Missing volume"), + mock_volume, ] self.mock_conn.storagePoolLookupByName.side_effect = [ - mock_pool, - mock_pool, - mock_pool, - self.mock_libvirt.libvirtError("Missing pool"), + mock_pool, + mock_pool, + mock_pool, + self.mock_libvirt.libvirtError("Missing pool"), ] # pylint: enable=no-member - self.assertTrue(virt.volume_delete('default', 'test_volume')) - self.assertFalse(virt.volume_delete('default', 'test_volume')) + self.assertTrue(virt.volume_delete("default", "test_volume")) + self.assertFalse(virt.volume_delete("default", "test_volume")) with self.assertRaises(self.mock_libvirt.libvirtError): - virt.volume_delete('default', 'missing') - virt.volume_delete('missing', 'test_volume') + virt.volume_delete("default", "missing") + virt.volume_delete("missing", "test_volume") self.assertEqual(mock_delete.call_count, 2) def test_pool_capabilities(self): - ''' + """ Test virt.pool_capabilities where libvirt has the pool-capabilities feature - ''' - xml_caps = ''' + """ + xml_caps = """ <storagepoolCapabilities> <pool type='disk' supported='yes'> <poolOptions> @@ -3555,113 +5076,170 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): <pool type='sheepdog' supported='no'> </pool> </storagepoolCapabilities> - ''' + """ self.mock_conn.getStoragePoolCapabilities = MagicMock(return_value=xml_caps) actual = virt.pool_capabilities() - self.assertEqual({ - 'computed': False, - 'pool_types': [{ - 'name': 'disk', - 'supported': True, - 'options': { - 'pool': { - 'default_format': 'unknown', - 'sourceFormatType': ['unknown', 'dos', 'dvh'] - }, - 'volume': { - 'default_format': 'none', - 'targetFormatType': ['none', 'linux'] - } - } - }, - { - 'name': 'iscsi', - 'supported': True, - }, - { - 'name': 'rbd', - 'supported': True, - 'options': { - 'volume': { - 'default_format': 'raw', - 'targetFormatType': [] - } - } - }, + self.assertEqual( { - 'name': 'sheepdog', - 'supported': False, + "computed": False, + "pool_types": [ + { + "name": "disk", + "supported": True, + "options": { + "pool": { + "default_format": "unknown", + "sourceFormatType": ["unknown", "dos", "dvh"], + }, + "volume": { + "default_format": "none", + "targetFormatType": ["none", "linux"], + }, + }, + }, + {"name": "iscsi", "supported": True}, + { + "name": "rbd", + "supported": True, + "options": { + "volume": {"default_format": "raw", "targetFormatType": []} + }, + }, + {"name": "sheepdog", "supported": False}, + ], }, - ]}, actual) + actual, + ) - @patch('salt.modules.virt.get_hypervisor', return_value='kvm') + @patch("salt.modules.virt.get_hypervisor", return_value="kvm") def test_pool_capabilities_computed(self, mock_get_hypervisor): - ''' + """ Test virt.pool_capabilities where libvirt doesn't have the pool-capabilities feature - ''' + """ self.mock_conn.getLibVersion = MagicMock(return_value=4006000) del self.mock_conn.getStoragePoolCapabilities actual = virt.pool_capabilities() - self.assertTrue(actual['computed']) - backends = actual['pool_types'] + self.assertTrue(actual["computed"]) + backends = actual["pool_types"] # libvirt version matching check - self.assertFalse([backend for backend in backends if backend['name'] == 'iscsi-direct'][0]['supported']) - self.assertTrue([backend for backend in backends if backend['name'] == 'gluster'][0]['supported']) - self.assertFalse([backend for backend in backends if backend['name'] == 'zfs'][0]['supported']) + self.assertFalse( + [backend for backend in backends if backend["name"] == "iscsi-direct"][0][ + "supported" + ] + ) + self.assertTrue( + [backend for backend in backends if backend["name"] == "gluster"][0][ + "supported" + ] + ) + self.assertFalse( + [backend for backend in backends if backend["name"] == "zfs"][0][ + "supported" + ] + ) # test case matching other hypervisors - mock_get_hypervisor.return_value = 'xen' - backends = virt.pool_capabilities()['pool_types'] - self.assertFalse([backend for backend in backends if backend['name'] == 'gluster'][0]['supported']) + mock_get_hypervisor.return_value = "xen" + backends = virt.pool_capabilities()["pool_types"] + self.assertFalse( + [backend for backend in backends if backend["name"] == "gluster"][0][ + "supported" + ] + ) - mock_get_hypervisor.return_value = 'bhyve' - backends = virt.pool_capabilities()['pool_types'] - self.assertFalse([backend for backend in backends if backend['name'] == 'gluster'][0]['supported']) - self.assertTrue([backend for backend in backends if backend['name'] == 'zfs'][0]['supported']) + mock_get_hypervisor.return_value = "bhyve" + backends = virt.pool_capabilities()["pool_types"] + self.assertFalse( + [backend for backend in backends if backend["name"] == "gluster"][0][ + "supported" + ] + ) + self.assertTrue( + [backend for backend in backends if backend["name"] == "zfs"][0][ + "supported" + ] + ) # Test options output - self.assertNotIn('options', [backend for backend in backends if backend['name'] == 'iscsi'][0]) - self.assertNotIn('pool', [backend for backend in backends if backend['name'] == 'dir'][0]['options']) - self.assertNotIn('volume', [backend for backend in backends if backend['name'] == 'logical'][0]['options']) - self.assertEqual({ - 'pool': { - 'default_format': 'auto', - 'sourceFormatType': ['auto', 'nfs', 'glusterfs', 'cifs'] + self.assertNotIn( + "options", + [backend for backend in backends if backend["name"] == "iscsi"][0], + ) + self.assertNotIn( + "pool", + [backend for backend in backends if backend["name"] == "dir"][0]["options"], + ) + self.assertNotIn( + "volume", + [backend for backend in backends if backend["name"] == "logical"][0][ + "options" + ], + ) + self.assertEqual( + { + "pool": { + "default_format": "auto", + "sourceFormatType": ["auto", "nfs", "glusterfs", "cifs"], + }, + "volume": { + "default_format": "raw", + "targetFormatType": [ + "none", + "raw", + "dir", + "bochs", + "cloop", + "dmg", + "iso", + "vpc", + "vdi", + "fat", + "vhd", + "ploop", + "cow", + "qcow", + "qcow2", + "qed", + "vmdk", + ], }, - 'volume': { - 'default_format': 'raw', - 'targetFormatType': ['none', 'raw', 'dir', 'bochs', 'cloop', 'dmg', 'iso', 'vpc', 'vdi', - 'fat', 'vhd', 'ploop', 'cow', 'qcow', 'qcow2', 'qed', 'vmdk'] - } }, - [backend for backend in backends if backend['name'] == 'netfs'][0]['options']) + [backend for backend in backends if backend["name"] == "netfs"][0][ + "options" + ], + ) def test_get_domain(self): - ''' + """ Test the virt._get_domain function - ''' + """ # Tests with no VM self.mock_conn.listDomainsID.return_value = [] self.mock_conn.listDefinedDomains.return_value = [] self.assertEqual([], virt._get_domain(self.mock_conn)) - self.assertRaisesRegex(CommandExecutionError, 'No virtual machines found.', - virt._get_domain, self.mock_conn, 'vm2') + self.assertRaisesRegex( + CommandExecutionError, + "No virtual machines found.", + virt._get_domain, + self.mock_conn, + "vm2", + ) # Test with active and inactive VMs self.mock_conn.listDomainsID.return_value = [1] def create_mock_vm(idx): mock_vm = MagicMock() - mock_vm.name.return_value = 'vm{0}'.format(idx) + mock_vm.name.return_value = "vm{0}".format(idx) return mock_vm mock_vms = [create_mock_vm(idx) for idx in range(3)] self.mock_conn.lookupByID.return_value = mock_vms[0] - self.mock_conn.listDefinedDomains.return_value = ['vm1', 'vm2'] + self.mock_conn.listDefinedDomains.return_value = ["vm1", "vm2"] self.mock_conn.lookupByName.side_effect = mock_vms self.assertEqual(mock_vms, virt._get_domain(self.mock_conn)) @@ -3672,11 +5250,297 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.mock_conn.lookupByName.return_value = None self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]] - self.assertEqual([mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, active=False)) + self.assertEqual( + [mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, active=False) + ) self.mock_conn.reset_mock() self.mock_conn.lookupByName.return_value = None self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]] - self.assertEqual([mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, 'vm1', 'vm2')) - self.assertRaisesRegex(CommandExecutionError, 'The VM "vm2" is not present', - virt._get_domain, self.mock_conn, 'vm2', inactive=False) + self.assertEqual( + [mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, "vm1", "vm2") + ) + self.assertRaisesRegex( + CommandExecutionError, + 'The VM "vm2" is not present', + virt._get_domain, + self.mock_conn, + "vm2", + inactive=False, + ) + + def test_volume_define(self): + """ + Test virt.volume_define function + """ + # Normal test case + pool_mock = MagicMock() + pool_mock.XMLDesc.return_value = "<pool type='dir'></pool>" + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + + self.assertTrue( + virt.volume_define( + "testpool", + "myvm_system.qcow2", + 8192, + allocation=4096, + format="qcow2", + type="file", + ) + ) + + expected_xml = ( + "<volume type='file'>\n" + " <name>myvm_system.qcow2</name>\n" + " <source>\n" + " </source>\n" + " <capacity unit='KiB'>8388608</capacity>\n" + " <allocation unit='KiB'>4194304</allocation>\n" + " <target>\n" + " <format type='qcow2'/>\n" + " </target>\n" + "</volume>" + ) + + pool_mock.createXML.assert_called_once_with(expected_xml, 0) + + # backing store test case + pool_mock.reset_mock() + self.assertTrue( + virt.volume_define( + "testpool", + "myvm_system.qcow2", + 8192, + allocation=4096, + format="qcow2", + type="file", + backing_store={"path": "/path/to/base.raw", "format": "raw"}, + ) + ) + + expected_xml = ( + "<volume type='file'>\n" + " <name>myvm_system.qcow2</name>\n" + " <source>\n" + " </source>\n" + " <capacity unit='KiB'>8388608</capacity>\n" + " <allocation unit='KiB'>4194304</allocation>\n" + " <target>\n" + " <format type='qcow2'/>\n" + " </target>\n" + " <backingStore>\n" + " <path>/path/to/base.raw</path>\n" + " <format type='raw'/>\n" + " </backingStore>\n" + "</volume>" + ) + + pool_mock.createXML.assert_called_once_with(expected_xml, 0) + + # logical pool test case + pool_mock.reset_mock() + pool_mock.XMLDesc.return_value = "<pool type='logical'></pool>" + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + + self.assertTrue( + virt.volume_define( + "testVG", + "myvm_system", + 8192, + backing_store={"path": "/dev/testVG/base"}, + ) + ) + + expected_xml = ( + "<volume>\n" + " <name>myvm_system</name>\n" + " <source>\n" + " </source>\n" + " <capacity unit='KiB'>8388608</capacity>\n" + " <allocation unit='KiB'>8388608</allocation>\n" + " <target>\n" + " </target>\n" + " <backingStore>\n" + " <path>/dev/testVG/base</path>\n" + " </backingStore>\n" + "</volume>" + ) + + pool_mock.createXML.assert_called_once_with(expected_xml, 0) + + def test_volume_upload(self): + """ + Test virt.volume_upload function + """ + pool_mock = MagicMock() + vol_mock = MagicMock() + pool_mock.storageVolLookupByName.return_value = vol_mock + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + stream_mock = MagicMock() + self.mock_conn.newStream.return_value = stream_mock + + open_mock = MagicMock() + close_mock = MagicMock() + with patch.dict( + os.__dict__, {"open": open_mock, "close": close_mock} + ): # pylint: disable=no-member + # Normal case + self.assertTrue(virt.volume_upload("pool0", "vol1.qcow2", "/path/to/file")) + stream_mock.sendAll.assert_called_once() + stream_mock.finish.assert_called_once() + self.mock_conn.close.assert_called_once() + vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0) + + # Sparse upload case + stream_mock.sendAll.reset_mock() + vol_mock.upload.reset_mock() + self.assertTrue( + virt.volume_upload( + "pool0", + "vol1.qcow2", + "/path/to/file", + offset=123, + length=456, + sparse=True, + ) + ) + stream_mock.sendAll.assert_not_called() + stream_mock.sparseSendAll.assert_called_once() + vol_mock.upload.assert_called_once_with( + stream_mock, + 123, + 456, + self.mock_libvirt.VIR_STORAGE_VOL_UPLOAD_SPARSE_STREAM, + ) + + # Upload unsupported case + vol_mock.upload.side_effect = self.mock_libvirt.libvirtError("Unsupported") + self.assertRaisesRegex( + CommandExecutionError, + "Unsupported", + virt.volume_upload, + "pool0", + "vol1.qcow2", + "/path/to/file", + ) + + def test_get_disks(self): + """ + Test the virt.get_disks function + """ + # test with volumes + vm_def = """<domain type='kvm' id='3'> + <name>srv01</name> + <devices> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2' cache='none' io='native'/> + <source pool='default' volume='srv01_system'/> + <backingStore/> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2' cache='none' io='native'/> + <source pool='default' volume='srv01_data'/> + <backingStore type='file' index='1'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/vol01'/> + <backingStore/> + </backingStore> + <target dev='vdb' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2' cache='none' io='native'/> + <source pool='default' volume='vm05_system'/> + <backingStore type='file' index='1'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/vm04_system.qcow2'/> + <backingStore type='file' index='2'> + <format type='raw'/> + <source file='/var/testsuite-data/disk-image-template.raw'/> + <backingStore/> + </backingStore> + </backingStore> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='network' device='cdrom'> + <driver name='qemu' type='raw' cache='none' io='native'/> + <source protocol='http' name='/pub/iso/myimage.iso' query='foo=bar&baz=flurb' index='1'> + <host name='dev-srv.tf.local' port='80'/> + </source> + <target dev='hda' bus='ide'/> + <readonly/> + <alias name='ide0-0-0'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + </devices> + </domain> + """ + self.set_mock_vm("srv01", vm_def) + + pool_mock = MagicMock() + pool_mock.storageVolLookupByName.return_value.info.return_value = [ + 0, + 1234567, + 12345, + ] + pool_mock.storageVolLookupByName.return_value.XMLDesc.side_effect = [ + "<volume />", + """ + <volume> + <backingStore> + <path>/var/lib/libvirt/images/vol01</path> + <format type="qcow2"/> + </backingStore> + </volume>""", + ] + self.mock_conn.storagePoolLookupByName.return_value = pool_mock + + self.assertDictEqual( + virt.get_disks("srv01"), + { + "vda": { + "type": "disk", + "file": "default/srv01_system", + "file format": "qcow2", + "disk size": 12345, + "virtual size": 1234567, + }, + "vdb": { + "type": "disk", + "file": "default/srv01_data", + "file format": "qcow2", + "disk size": 12345, + "virtual size": 1234567, + "backing file": { + "file": "/var/lib/libvirt/images/vol01", + "file format": "qcow2", + }, + }, + "vdc": { + "type": "disk", + "file": "default/vm05_system", + "file format": "qcow2", + "disk size": 12345, + "virtual size": 1234567, + "backing file": { + "file": "/var/lib/libvirt/images/vm04_system.qcow2", + "file format": "qcow2", + "backing file": { + "file": "/var/testsuite-data/disk-image-template.raw", + "file format": "raw", + }, + }, + }, + "hda": { + "type": "cdrom", + "file format": "raw", + "file": "http://dev-srv.tf.local:80/pub/iso/myimage.iso?foo=bar&baz=flurb", + }, + }, + ) diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index 6727704494..c76f8a5fc0 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -1,55 +1,56 @@ # -*- coding: utf-8 -*- -''' +""" :codeauthor: Jayesh Kariya <jayeshk@saltstack.com> -''' +""" # Import Python libs from __future__ import absolute_import, print_function, unicode_literals -import tempfile -import shutil -# Import Salt Testing Libs -from tests.support.runtests import RUNTIME_VARS -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.unit import TestCase -from tests.support.mock import ( - MagicMock, - mock_open, - patch) +import shutil +import tempfile # Import Salt Libs import salt.states.virt as virt import salt.utils.files -from salt.exceptions import CommandExecutionError +from salt.exceptions import CommandExecutionError, SaltInvocationError # Import 3rd-party libs from salt.ext import six +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.mock import MagicMock, mock_open, patch + +# Import Salt Testing Libs +from tests.support.runtests import RUNTIME_VARS +from tests.support.unit import TestCase class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors - ''' + """ libvirt library mockup - ''' + """ + class libvirtError(Exception): # pylint: disable=invalid-name - ''' + """ libvirt error mockup - ''' + """ + def get_error_message(self): - ''' + """ Fake function return error message - ''' + """ return six.text_type(self) class LibvirtTestCase(TestCase, LoaderModuleMockMixin): - ''' + """ Test cases for salt.states.libvirt - ''' + """ + def setup_loader_modules(self): - self.mock_libvirt = LibvirtMock() # pylint: disable=attribute-defined-outside-init - self.addCleanup(delattr, self, 'mock_libvirt') - loader_globals = { - 'libvirt': self.mock_libvirt - } + self.mock_libvirt = ( + LibvirtMock() + ) # pylint: disable=attribute-defined-outside-init + self.addCleanup(delattr, self, "mock_libvirt") + loader_globals = {"libvirt": self.mock_libvirt} return {virt: loader_globals} @classmethod @@ -64,1831 +65,3252 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): # 'keys' function tests: 1 def test_keys(self): - ''' + """ Test to manage libvirt keys. - ''' - with patch('os.path.isfile', MagicMock(return_value=False)): - name = 'sunrise' - - ret = {'name': name, - 'result': True, - 'comment': '', - 'changes': {}} - - mock = MagicMock(side_effect=[[], ['libvirt.servercert.pem'], - {'libvirt.servercert.pem': 'A'}]) - with patch.dict(virt.__salt__, {'pillar.ext': mock}): - comt = ('All keys are correct') - ret.update({'comment': comt}) + """ + with patch("os.path.isfile", MagicMock(return_value=False)): + name = "sunrise" + + ret = {"name": name, "result": True, "comment": "", "changes": {}} + + mock = MagicMock( + side_effect=[ + [], + ["libvirt.servercert.pem"], + {"libvirt.servercert.pem": "A"}, + ] + ) + with patch.dict(virt.__salt__, {"pillar.ext": mock}): + comt = "All keys are correct" + ret.update({"comment": comt}) self.assertDictEqual(virt.keys(name, basepath=self.pki_dir), ret) - with patch.dict(virt.__opts__, {'test': True}): - comt = ('Libvirt keys are set to be updated') - ret.update({'comment': comt, 'result': None}) + with patch.dict(virt.__opts__, {"test": True}): + comt = "Libvirt keys are set to be updated" + ret.update({"comment": comt, "result": None}) self.assertDictEqual(virt.keys(name, basepath=self.pki_dir), ret) - with patch.dict(virt.__opts__, {'test': False}): - with patch.object(salt.utils.files, 'fopen', MagicMock(mock_open())): - comt = ('Updated libvirt certs and keys') - ret.update({'comment': comt, 'result': True, - 'changes': {'servercert': 'new'}}) - self.assertDictEqual(virt.keys(name, basepath=self.pki_dir), ret) + with patch.dict(virt.__opts__, {"test": False}): + with patch.object( + salt.utils.files, "fopen", MagicMock(mock_open()) + ): + comt = "Updated libvirt certs and keys" + ret.update( + { + "comment": comt, + "result": True, + "changes": {"servercert": "new"}, + } + ) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir), ret + ) def test_keys_with_expiration_days(self): - ''' + """ Test to manage libvirt keys. - ''' - with patch('os.path.isfile', MagicMock(return_value=False)): - name = 'sunrise' - - ret = {'name': name, - 'result': True, - 'comment': '', - 'changes': {}} - - mock = MagicMock(side_effect=[[], ['libvirt.servercert.pem'], - {'libvirt.servercert.pem': 'A'}]) - with patch.dict(virt.__salt__, {'pillar.ext': mock}): - comt = ('All keys are correct') - ret.update({'comment': comt}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - expiration_days=700), ret) - - with patch.dict(virt.__opts__, {'test': True}): - comt = ('Libvirt keys are set to be updated') - ret.update({'comment': comt, 'result': None}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - expiration_days=700), ret) - - with patch.dict(virt.__opts__, {'test': False}): - with patch.object(salt.utils.files, 'fopen', MagicMock(mock_open())): - comt = ('Updated libvirt certs and keys') - ret.update({'comment': comt, 'result': True, - 'changes': {'servercert': 'new'}}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - expiration_days=700), ret) + """ + with patch("os.path.isfile", MagicMock(return_value=False)): + name = "sunrise" + + ret = {"name": name, "result": True, "comment": "", "changes": {}} + + mock = MagicMock( + side_effect=[ + [], + ["libvirt.servercert.pem"], + {"libvirt.servercert.pem": "A"}, + ] + ) + with patch.dict(virt.__salt__, {"pillar.ext": mock}): + comt = "All keys are correct" + ret.update({"comment": comt}) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, expiration_days=700), ret + ) + + with patch.dict(virt.__opts__, {"test": True}): + comt = "Libvirt keys are set to be updated" + ret.update({"comment": comt, "result": None}) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, expiration_days=700), ret + ) + + with patch.dict(virt.__opts__, {"test": False}): + with patch.object( + salt.utils.files, "fopen", MagicMock(mock_open()) + ): + comt = "Updated libvirt certs and keys" + ret.update( + { + "comment": comt, + "result": True, + "changes": {"servercert": "new"}, + } + ) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, expiration_days=700), + ret, + ) def test_keys_with_state(self): - ''' + """ Test to manage libvirt keys. - ''' - with patch('os.path.isfile', MagicMock(return_value=False)): - name = 'sunrise' - - ret = {'name': name, - 'result': True, - 'comment': '', - 'changes': {}} - - mock = MagicMock(side_effect=[[], ['libvirt.servercert.pem'], - {'libvirt.servercert.pem': 'A'}]) - with patch.dict(virt.__salt__, {'pillar.ext': mock}): - comt = ('All keys are correct') - ret.update({'comment': comt}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - st='California'), ret) - - with patch.dict(virt.__opts__, {'test': True}): - comt = ('Libvirt keys are set to be updated') - ret.update({'comment': comt, 'result': None}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - st='California'), ret) - - with patch.dict(virt.__opts__, {'test': False}): - with patch.object(salt.utils.files, 'fopen', MagicMock(mock_open())): - comt = ('Updated libvirt certs and keys') - ret.update({'comment': comt, 'result': True, - 'changes': {'servercert': 'new'}}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - st='California'), ret) + """ + with patch("os.path.isfile", MagicMock(return_value=False)): + name = "sunrise" + + ret = {"name": name, "result": True, "comment": "", "changes": {}} + + mock = MagicMock( + side_effect=[ + [], + ["libvirt.servercert.pem"], + {"libvirt.servercert.pem": "A"}, + ] + ) + with patch.dict(virt.__salt__, {"pillar.ext": mock}): + comt = "All keys are correct" + ret.update({"comment": comt}) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, st="California"), ret + ) + + with patch.dict(virt.__opts__, {"test": True}): + comt = "Libvirt keys are set to be updated" + ret.update({"comment": comt, "result": None}) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, st="California"), ret + ) + + with patch.dict(virt.__opts__, {"test": False}): + with patch.object( + salt.utils.files, "fopen", MagicMock(mock_open()) + ): + comt = "Updated libvirt certs and keys" + ret.update( + { + "comment": comt, + "result": True, + "changes": {"servercert": "new"}, + } + ) + self.assertDictEqual( + virt.keys(name, basepath=self.pki_dir, st="California"), ret + ) def test_keys_with_all_options(self): - ''' + """ Test to manage libvirt keys. - ''' - with patch('os.path.isfile', MagicMock(return_value=False)): - name = 'sunrise' - - ret = {'name': name, - 'result': True, - 'comment': '', - 'changes': {}} - - mock = MagicMock(side_effect=[[], ['libvirt.servercert.pem'], - {'libvirt.servercert.pem': 'A'}]) - with patch.dict(virt.__salt__, {'pillar.ext': mock}): - comt = ('All keys are correct') - ret.update({'comment': comt}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - country='USA', - st='California', - locality='Los_Angeles', - organization='SaltStack', - expiration_days=700), ret) - - with patch.dict(virt.__opts__, {'test': True}): - comt = ('Libvirt keys are set to be updated') - ret.update({'comment': comt, 'result': None}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - country='USA', - st='California', - locality='Los_Angeles', - organization='SaltStack', - expiration_days=700), ret) - - with patch.dict(virt.__opts__, {'test': False}): - with patch.object(salt.utils.files, 'fopen', MagicMock(mock_open())): - comt = ('Updated libvirt certs and keys') - ret.update({'comment': comt, 'result': True, - 'changes': {'servercert': 'new'}}) - self.assertDictEqual(virt.keys(name, - basepath=self.pki_dir, - country='USA', - st='California', - locality='Los_Angeles', - organization='SaltStack', - expiration_days=700), ret) + """ + with patch("os.path.isfile", MagicMock(return_value=False)): + name = "sunrise" + + ret = {"name": name, "result": True, "comment": "", "changes": {}} + + mock = MagicMock( + side_effect=[ + [], + ["libvirt.servercert.pem"], + {"libvirt.servercert.pem": "A"}, + ] + ) + with patch.dict(virt.__salt__, {"pillar.ext": mock}): + comt = "All keys are correct" + ret.update({"comment": comt}) + self.assertDictEqual( + virt.keys( + name, + basepath=self.pki_dir, + country="USA", + st="California", + locality="Los_Angeles", + organization="SaltStack", + expiration_days=700, + ), + ret, + ) + + with patch.dict(virt.__opts__, {"test": True}): + comt = "Libvirt keys are set to be updated" + ret.update({"comment": comt, "result": None}) + self.assertDictEqual( + virt.keys( + name, + basepath=self.pki_dir, + country="USA", + st="California", + locality="Los_Angeles", + organization="SaltStack", + expiration_days=700, + ), + ret, + ) + + with patch.dict(virt.__opts__, {"test": False}): + with patch.object( + salt.utils.files, "fopen", MagicMock(mock_open()) + ): + comt = "Updated libvirt certs and keys" + ret.update( + { + "comment": comt, + "result": True, + "changes": {"servercert": "new"}, + } + ) + self.assertDictEqual( + virt.keys( + name, + basepath=self.pki_dir, + country="USA", + st="California", + locality="Los_Angeles", + organization="SaltStack", + expiration_days=700, + ), + ret, + ) def test_defined(self): - ''' + """ defined state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True, - 'comment': 'myvm is running'} - with patch.dict(virt.__opts__, {'test': False}): + """ + ret = { + "name": "myvm", + "changes": {}, + "result": True, + "comment": "myvm is running", + } + with patch.dict(virt.__opts__, {"test": False}): # no change test init_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': MagicMock(return_value={'definition': False}), - }): - ret.update({'changes': {'myvm': {'definition': False}}, - 'comment': 'Domain myvm unchanged'}) - self.assertDictEqual(virt.defined('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": MagicMock(return_value={"definition": False}), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": False}}, + "comment": "Domain myvm unchanged", + } + ) + self.assertDictEqual(virt.defined("myvm"), ret) # Test defining a guest with connection details init_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=[]), - 'virt.init': init_mock, - 'virt.update': MagicMock(side_effect=CommandExecutionError('not found')), - }): - ret.update({'changes': {'myvm': {'definition': True}}, - 'comment': 'Domain myvm defined'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - ifaces = [{ - 'name': 'eth0', - 'mac': '01:23:45:67:89:AB' - }, - { - 'name': 'eth1', - 'type': 'network', - 'source': 'admin' - }] - graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} - self.assertDictEqual(virt.defined('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - vm_type='qemu', - disk_profile='prod', - disks=disks, - nic_profile='prod', - interfaces=ifaces, - graphics=graphics, - seed=False, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret'), ret) - init_mock.assert_called_with('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - disk='prod', - disks=disks, - nic='prod', - interfaces=ifaces, - graphics=graphics, - hypervisor='qemu', - seed=False, - boot=None, - install=False, - start=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=[]), + "virt.init": init_mock, + "virt.update": MagicMock( + side_effect=CommandExecutionError("not found") + ), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True}}, + "comment": "Domain myvm defined", + } + ) + disks = [ + { + "name": "system", + "size": 8192, + "overlay_image": True, + "pool": "default", + "image": "/path/to/image.qcow2", + }, + {"name": "data", "size": 16834}, + ] + ifaces = [ + {"name": "eth0", "mac": "01:23:45:67:89:AB"}, + {"name": "eth1", "type": "network", "source": "admin"}, + ] + graphics = { + "type": "spice", + "listen": {"type": "address", "address": "192.168.0.1"}, + } + self.assertDictEqual( + virt.defined( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + vm_type="qemu", + disk_profile="prod", + disks=disks, + nic_profile="prod", + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ), + ret, + ) + init_mock.assert_called_with( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + disk="prod", + disks=disks, + nic="prod", + interfaces=ifaces, + graphics=graphics, + hypervisor="qemu", + seed=False, + boot=None, + install=False, + start=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ) # Working update case when running - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": MagicMock( + return_value={"definition": True, "cpu": True} + ), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "cpu": True}}, + "result": True, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) # Working update case when running with boot params boot = { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + "kernel": "/root/f8-i386-vmlinuz", + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", } - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}) - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.defined('myvm', boot=boot), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": MagicMock( + return_value={"definition": True, "cpu": True} + ), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "cpu": True}}, + "result": True, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.defined("myvm", boot=boot), ret) # Working update case when stopped - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': MagicMock(return_value={'definition': True}) - }): - ret.update({'changes': {'myvm': {'definition': True}}, - 'result': True, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": MagicMock(return_value={"definition": True}), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True}}, + "result": True, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) # Failed live update case - update_mock = MagicMock(return_value={'definition': True, 'cpu': False, 'errors': ['some error']}) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': update_mock, - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': False, 'errors': ['some error']}}, - 'result': True, - 'comment': 'Domain myvm updated with live update(s) failures'}) - self.assertDictEqual(virt.defined('myvm', cpu=2), ret) - update_mock.assert_called_with('myvm', cpu=2, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=False) + update_mock = MagicMock( + return_value={ + "definition": True, + "cpu": False, + "errors": ["some error"], + } + ) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": update_mock, + }, + ): + ret.update( + { + "changes": { + "myvm": { + "definition": True, + "cpu": False, + "errors": ["some error"], + } + }, + "result": True, + "comment": "Domain myvm updated with live update(s) failures", + } + ) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) + update_mock.assert_called_with( + "myvm", + cpu=2, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=False, + ) # Failed definition update case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': MagicMock(side_effect=[self.mock_libvirt.libvirtError('error message')]) - }): - ret.update({'changes': {}, - 'result': False, - 'comment': 'error message'}) - self.assertDictEqual(virt.defined('myvm', cpu=2), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": MagicMock( + side_effect=[self.mock_libvirt.libvirtError("error message")] + ), + }, + ): + ret.update({"changes": {}, "result": False, "comment": "error message"}) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) # Test dry-run mode - with patch.dict(virt.__opts__, {'test': True}): + with patch.dict(virt.__opts__, {"test": True}): # Guest defined case init_mock = MagicMock(return_value=True) - update_mock = MagicMock(side_effect=CommandExecutionError('not found')) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=[]), - 'virt.init': init_mock, - 'virt.update': update_mock, - }): - ret.update({'changes': {'myvm': {'definition': True}}, - 'result': None, - 'comment': 'Domain myvm defined'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - ifaces = [{ - 'name': 'eth0', - 'mac': '01:23:45:67:89:AB' - }, - { - 'name': 'eth1', - 'type': 'network', - 'source': 'admin' - }] - graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} - self.assertDictEqual(virt.defined('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - vm_type='qemu', - disk_profile='prod', - disks=disks, - nic_profile='prod', - interfaces=ifaces, - graphics=graphics, - seed=False, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret'), ret) + update_mock = MagicMock(side_effect=CommandExecutionError("not found")) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=[]), + "virt.init": init_mock, + "virt.update": update_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True}}, + "result": None, + "comment": "Domain myvm defined", + } + ) + disks = [ + { + "name": "system", + "size": 8192, + "overlay_image": True, + "pool": "default", + "image": "/path/to/image.qcow2", + }, + {"name": "data", "size": 16834}, + ] + ifaces = [ + {"name": "eth0", "mac": "01:23:45:67:89:AB"}, + {"name": "eth1", "type": "network", "source": "admin"}, + ] + graphics = { + "type": "spice", + "listen": {"type": "address", "address": "192.168.0.1"}, + } + self.assertDictEqual( + virt.defined( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + vm_type="qemu", + disk_profile="prod", + disks=disks, + nic_profile="prod", + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ), + ret, + ) init_mock.assert_not_called() update_mock.assert_not_called() # Guest update case - update_mock = MagicMock(return_value={'definition': True}) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': update_mock - }): - ret.update({'changes': {'myvm': {'definition': True}}, - 'result': None, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.defined('myvm', cpu=2), ret) - update_mock.assert_called_with('myvm', cpu=2, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=True) + update_mock = MagicMock(return_value={"definition": True}) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": update_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True}}, + "result": None, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) + update_mock.assert_called_with( + "myvm", + cpu=2, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=True, + ) # No changes case - update_mock = MagicMock(return_value={'definition': False}) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm']), - 'virt.update': update_mock, - }): - ret.update({'changes': {'myvm': {'definition': False}}, - 'result': True, - 'comment': 'Domain myvm unchanged'}) - self.assertDictEqual(virt.defined('myvm'), ret) - update_mock.assert_called_with('myvm', cpu=None, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=True) + update_mock = MagicMock(return_value={"definition": False}) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm"]), + "virt.update": update_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": False}}, + "result": True, + "comment": "Domain myvm unchanged", + } + ) + self.assertDictEqual(virt.defined("myvm"), ret) + update_mock.assert_called_with( + "myvm", + cpu=None, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=True, + ) def test_running(self): - ''' + """ running state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True, - 'comment': 'myvm is running'} - with patch.dict(virt.__opts__, {'test': False}): + """ + ret = { + "name": "myvm", + "changes": {}, + "result": True, + "comment": "myvm is running", + } + with patch.dict(virt.__opts__, {"test": False}): # Test starting an existing guest without changing it - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': MagicMock(return_value=0), - 'virt.update': MagicMock(return_value={'definition': False}), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {'started': True}}, - 'comment': 'Domain myvm started'}) - self.assertDictEqual(virt.running('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.start": MagicMock(return_value=0), + "virt.update": MagicMock(return_value={"definition": False}), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": {"myvm": {"started": True}}, + "comment": "Domain myvm started", + } + ) + self.assertDictEqual(virt.running("myvm"), ret) # Test defining and starting a guest the old way init_mock = MagicMock(return_value=True) start_mock = MagicMock(return_value=0) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.init': init_mock, - 'virt.start': start_mock, - 'virt.list_domains': MagicMock(return_value=[]), - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'comment': 'Domain myvm defined and started'}) - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - image='/path/to/img.qcow2'), ret) - init_mock.assert_called_with('myvm', cpu=2, mem=2048, - os_type=None, arch=None, boot=None, - disk=None, disks=[{'name': 'system', 'image': '/path/to/img.qcow2'}], nic=None, interfaces=None, - graphics=None, hypervisor=None, start=False, - seed=True, install=True, pub_key=None, priv_key=None, - connection=None, username=None, password=None,) - start_mock.assert_called_with('myvm', connection=None, username=None, password=None) - - # Test image parameter with disks with defined image - init_mock = MagicMock(return_value=True) - start_mock = MagicMock(return_value=0) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.init': init_mock, - 'virt.start': start_mock, - 'virt.list_domains': MagicMock(return_value=[]), - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'comment': 'Domain myvm defined and started'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - disks=disks, - image='/path/to/img.qcow2'), ret) - init_mock.assert_called_with('myvm', cpu=2, mem=2048, - os_type=None, arch=None, boot=None, - disk=None, disks=disks, nic=None, interfaces=None, - graphics=None, hypervisor=None, start=False, - seed=True, install=True, pub_key=None, priv_key=None, - connection=None, username=None, password=None,) - start_mock.assert_called_with('myvm', connection=None, username=None, password=None) - - # Test image parameter with disks without defined image - init_mock = MagicMock(return_value=True) - start_mock = MagicMock(return_value=0) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.init': init_mock, - 'virt.start': start_mock, - 'virt.list_domains': MagicMock(return_value=[]), - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'comment': 'Domain myvm defined and started'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - }, - { - 'name': 'data', - 'size': 16834 - }] - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - disks=disks, - image='/path/to/img.qcow2'), ret) - init_mock.assert_called_with('myvm', cpu=2, mem=2048, - os_type=None, arch=None, boot=None, - disk=None, - disks=[{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/img.qcow2', - }, - { - 'name': 'data', - 'size': 16834 - }], - nic=None, interfaces=None, - graphics=None, hypervisor=None, start=False, - seed=True, install=True, pub_key=None, priv_key=None, - connection=None, username=None, password=None,) - start_mock.assert_called_with('myvm', connection=None, username=None, password=None) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.init": init_mock, + "virt.start": start_mock, + "virt.list_domains": MagicMock(return_value=[]), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "started": True}}, + "comment": "Domain myvm defined and started", + } + ) + self.assertDictEqual( + virt.running( + "myvm", + cpu=2, + mem=2048, + disks=[{"name": "system", "image": "/path/to/img.qcow2"}], + ), + ret, + ) + init_mock.assert_called_with( + "myvm", + cpu=2, + mem=2048, + os_type=None, + arch=None, + boot=None, + disk=None, + disks=[{"name": "system", "image": "/path/to/img.qcow2"}], + nic=None, + interfaces=None, + graphics=None, + hypervisor=None, + start=False, + seed=True, + install=True, + pub_key=None, + priv_key=None, + connection=None, + username=None, + password=None, + ) + start_mock.assert_called_with( + "myvm", connection=None, username=None, password=None + ) # Test defining and starting a guest the new way with connection details init_mock.reset_mock() start_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.init': init_mock, - 'virt.start': start_mock, - 'virt.list_domains': MagicMock(return_value=[]), - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'comment': 'Domain myvm defined and started'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - ifaces = [{ - 'name': 'eth0', - 'mac': '01:23:45:67:89:AB' - }, - { - 'name': 'eth1', - 'type': 'network', - 'source': 'admin' - }] - graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - vm_type='qemu', - disk_profile='prod', - disks=disks, - nic_profile='prod', - interfaces=ifaces, - graphics=graphics, - seed=False, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret'), ret) - init_mock.assert_called_with('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - disk='prod', - disks=disks, - nic='prod', - interfaces=ifaces, - graphics=graphics, - hypervisor='qemu', - seed=False, - boot=None, - install=False, - start=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret') - start_mock.assert_called_with('myvm', - connection='someconnection', - username='libvirtuser', - password='supersecret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.init": init_mock, + "virt.start": start_mock, + "virt.list_domains": MagicMock(return_value=[]), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "started": True}}, + "comment": "Domain myvm defined and started", + } + ) + disks = [ + { + "name": "system", + "size": 8192, + "overlay_image": True, + "pool": "default", + "image": "/path/to/image.qcow2", + }, + {"name": "data", "size": 16834}, + ] + ifaces = [ + {"name": "eth0", "mac": "01:23:45:67:89:AB"}, + {"name": "eth1", "type": "network", "source": "admin"}, + ] + graphics = { + "type": "spice", + "listen": {"type": "address", "address": "192.168.0.1"}, + } + self.assertDictEqual( + virt.running( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + vm_type="qemu", + disk_profile="prod", + disks=disks, + nic_profile="prod", + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ), + ret, + ) + init_mock.assert_called_with( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + disk="prod", + disks=disks, + nic="prod", + interfaces=ifaces, + graphics=graphics, + hypervisor="qemu", + seed=False, + boot=None, + install=False, + start=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ) + start_mock.assert_called_with( + "myvm", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ) # Test with existing guest, but start raising an error - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.update': MagicMock(return_value={'definition': False}), - 'virt.start': MagicMock(side_effect=[self.mock_libvirt.libvirtError('libvirt error msg')]), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {}}, 'result': False, 'comment': 'libvirt error msg'}) - self.assertDictEqual(virt.running('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.update": MagicMock(return_value={"definition": False}), + "virt.start": MagicMock( + side_effect=[ + self.mock_libvirt.libvirtError("libvirt error msg") + ] + ), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": {"myvm": {}}, + "result": False, + "comment": "libvirt error msg", + } + ) + self.assertDictEqual(virt.running("myvm"), ret) # Working update case when running - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.update": MagicMock( + return_value={"definition": True, "cpu": True} + ), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "cpu": True}}, + "result": True, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret) # Working update case when running with boot params boot = { - 'kernel': '/root/f8-i386-vmlinuz', - 'initrd': '/root/f8-i386-initrd', - 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + "kernel": "/root/f8-i386-vmlinuz", + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", } - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(return_value={'definition': True, 'cpu': True}), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': True}}, - 'result': True, - 'comment': 'Domain myvm updated'}) - self.assertDictEqual(virt.running('myvm', boot=boot, update=True), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.update": MagicMock( + return_value={"definition": True, "cpu": True} + ), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "cpu": True}}, + "result": True, + "comment": "Domain myvm updated", + } + ) + self.assertDictEqual(virt.running("myvm", boot=boot, update=True), ret) # Working update case when stopped - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': MagicMock(return_value=0), - 'virt.update': MagicMock(return_value={'definition': True}), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'result': True, - 'comment': 'Domain myvm updated and started'}) - self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.start": MagicMock(return_value=0), + "virt.update": MagicMock(return_value={"definition": True}), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "started": True}}, + "result": True, + "comment": "Domain myvm updated and started", + } + ) + self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret) # Failed live update case - update_mock = MagicMock(return_value={'definition': True, 'cpu': False, 'errors': ['some error']}) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': update_mock, - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {'myvm': {'definition': True, 'cpu': False, 'errors': ['some error']}}, - 'result': True, - 'comment': 'Domain myvm updated with live update(s) failures'}) - self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) - update_mock.assert_called_with('myvm', cpu=2, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=False) + update_mock = MagicMock( + return_value={ + "definition": True, + "cpu": False, + "errors": ["some error"], + } + ) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.update": update_mock, + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update( + { + "changes": { + "myvm": { + "definition": True, + "cpu": False, + "errors": ["some error"], + } + }, + "result": True, + "comment": "Domain myvm updated with live update(s) failures", + } + ) + self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret) + update_mock.assert_called_with( + "myvm", + cpu=2, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=False, + ) # Failed definition update case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': MagicMock(side_effect=[self.mock_libvirt.libvirtError('error message')]), - 'virt.list_domains': MagicMock(return_value=['myvm']), - }): - ret.update({'changes': {}, - 'result': False, - 'comment': 'error message'}) - self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.update": MagicMock( + side_effect=[self.mock_libvirt.libvirtError("error message")] + ), + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): + ret.update({"changes": {}, "result": False, "comment": "error message"}) + self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret) # Test dry-run mode - with patch.dict(virt.__opts__, {'test': True}): + with patch.dict(virt.__opts__, {"test": True}): # Guest defined case init_mock = MagicMock(return_value=True) start_mock = MagicMock(return_value=0) list_mock = MagicMock(return_value=[]) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.init': init_mock, - 'virt.start': start_mock, - 'virt.list_domains': list_mock, - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'result': None, - 'comment': 'Domain myvm defined and started'}) - disks = [{ - 'name': 'system', - 'size': 8192, - 'overlay_image': True, - 'pool': 'default', - 'image': '/path/to/image.qcow2' - }, - { - 'name': 'data', - 'size': 16834 - }] - ifaces = [{ - 'name': 'eth0', - 'mac': '01:23:45:67:89:AB' - }, - { - 'name': 'eth1', - 'type': 'network', - 'source': 'admin' - }] - graphics = {'type': 'spice', 'listen': {'type': 'address', 'address': '192.168.0.1'}} - self.assertDictEqual(virt.running('myvm', - cpu=2, - mem=2048, - os_type='linux', - arch='i686', - vm_type='qemu', - disk_profile='prod', - disks=disks, - nic_profile='prod', - interfaces=ifaces, - graphics=graphics, - seed=False, - install=False, - pub_key='/path/to/key.pub', - priv_key='/path/to/key', - connection='someconnection', - username='libvirtuser', - password='supersecret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.init": init_mock, + "virt.start": start_mock, + "virt.list_domains": list_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "started": True}}, + "result": None, + "comment": "Domain myvm defined and started", + } + ) + disks = [ + { + "name": "system", + "size": 8192, + "overlay_image": True, + "pool": "default", + "image": "/path/to/image.qcow2", + }, + {"name": "data", "size": 16834}, + ] + ifaces = [ + {"name": "eth0", "mac": "01:23:45:67:89:AB"}, + {"name": "eth1", "type": "network", "source": "admin"}, + ] + graphics = { + "type": "spice", + "listen": {"type": "address", "address": "192.168.0.1"}, + } + self.assertDictEqual( + virt.running( + "myvm", + cpu=2, + mem=2048, + os_type="linux", + arch="i686", + vm_type="qemu", + disk_profile="prod", + disks=disks, + nic_profile="prod", + interfaces=ifaces, + graphics=graphics, + seed=False, + install=False, + pub_key="/path/to/key.pub", + priv_key="/path/to/key", + connection="someconnection", + username="libvirtuser", + password="supersecret", + ), + ret, + ) init_mock.assert_not_called() start_mock.assert_not_called() # Guest update case - update_mock = MagicMock(return_value={'definition': True}) + update_mock = MagicMock(return_value={"definition": True}) start_mock = MagicMock(return_value=0) - list_mock = MagicMock(return_value=['myvm']) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'stopped'}), - 'virt.start': start_mock, - 'virt.update': update_mock, - 'virt.list_domains': list_mock, - }): - ret.update({'changes': {'myvm': {'definition': True, 'started': True}}, - 'result': None, - 'comment': 'Domain myvm updated and started'}) - self.assertDictEqual(virt.running('myvm', cpu=2, update=True), ret) - update_mock.assert_called_with('myvm', cpu=2, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=True) + list_mock = MagicMock(return_value=["myvm"]) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}), + "virt.start": start_mock, + "virt.update": update_mock, + "virt.list_domains": list_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": True, "started": True}}, + "result": None, + "comment": "Domain myvm updated and started", + } + ) + self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret) + update_mock.assert_called_with( + "myvm", + cpu=2, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=True, + ) start_mock.assert_not_called() # No changes case - update_mock = MagicMock(return_value={'definition': False}) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.update': update_mock, - 'virt.list_domains': list_mock, - }): - ret.update({'changes': {'myvm': {'definition': False}}, - 'result': True, - 'comment': 'Domain myvm exists and is running'}) - self.assertDictEqual(virt.running('myvm', update=True), ret) - update_mock.assert_called_with('myvm', cpu=None, mem=None, - disk_profile=None, disks=None, nic_profile=None, interfaces=None, - graphics=None, live=True, - connection=None, username=None, password=None, - boot=None, test=True) + update_mock = MagicMock(return_value={"definition": False}) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.update": update_mock, + "virt.list_domains": list_mock, + }, + ): + ret.update( + { + "changes": {"myvm": {"definition": False}}, + "result": True, + "comment": "Domain myvm exists and is running", + } + ) + self.assertDictEqual(virt.running("myvm", update=True), ret) + update_mock.assert_called_with( + "myvm", + cpu=None, + mem=None, + disk_profile=None, + disks=None, + nic_profile=None, + interfaces=None, + graphics=None, + live=True, + connection=None, + username=None, + password=None, + boot=None, + test=True, + ) def test_stopped(self): - ''' + """ stopped state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True} + """ + ret = {"name": "myvm", "changes": {}, "result": True} shutdown_mock = MagicMock(return_value=True) # Normal case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.shutdown': shutdown_mock - }): - ret.update({'changes': { - 'stopped': [{'domain': 'myvm', 'shutdown': True}] - }, - 'comment': 'Machine has been shut down'}) - self.assertDictEqual(virt.stopped('myvm'), ret) - shutdown_mock.assert_called_with('myvm', connection=None, username=None, password=None) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.shutdown": shutdown_mock, + }, + ): + ret.update( + { + "changes": {"stopped": [{"domain": "myvm", "shutdown": True}]}, + "comment": "Machine has been shut down", + } + ) + self.assertDictEqual(virt.stopped("myvm"), ret) + shutdown_mock.assert_called_with( + "myvm", connection=None, username=None, password=None + ) # Normal case with user-provided connection parameters - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.shutdown': shutdown_mock, - }): - self.assertDictEqual(virt.stopped('myvm', - connection='myconnection', - username='user', - password='secret'), ret) - shutdown_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.shutdown": shutdown_mock, + }, + ): + self.assertDictEqual( + virt.stopped( + "myvm", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + shutdown_mock.assert_called_with( + "myvm", connection="myconnection", username="user", password="secret" + ) # Case where an error occurred during the shutdown - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.shutdown': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, - 'result': False, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.stopped('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.shutdown": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update( + { + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "result": False, + "comment": "No changes had happened", + } + ) + self.assertDictEqual(virt.stopped("myvm"), ret) # Case there the domain doesn't exist - with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member - ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.stopped('myvm'), ret) + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): # pylint: disable=no-member + ret.update( + {"changes": {}, "result": False, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.stopped("myvm"), ret) # Case where the domain is already stopped - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'shutdown'}) - }): - ret.update({'changes': {}, - 'result': True, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.stopped('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "shutdown"}), + }, + ): + ret.update( + {"changes": {}, "result": True, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.stopped("myvm"), ret) def test_powered_off(self): - ''' + """ powered_off state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True} + """ + ret = {"name": "myvm", "changes": {}, "result": True} stop_mock = MagicMock(return_value=True) # Normal case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.stop': stop_mock - }): - ret.update({'changes': { - 'unpowered': [{'domain': 'myvm', 'stop': True}] - }, - 'comment': 'Machine has been powered off'}) - self.assertDictEqual(virt.powered_off('myvm'), ret) - stop_mock.assert_called_with('myvm', connection=None, username=None, password=None) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.stop": stop_mock, + }, + ): + ret.update( + { + "changes": {"unpowered": [{"domain": "myvm", "stop": True}]}, + "comment": "Machine has been powered off", + } + ) + self.assertDictEqual(virt.powered_off("myvm"), ret) + stop_mock.assert_called_with( + "myvm", connection=None, username=None, password=None + ) # Normal case with user-provided connection parameters - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.stop': stop_mock, - }): - self.assertDictEqual(virt.powered_off('myvm', - connection='myconnection', - username='user', - password='secret'), ret) - stop_mock.assert_called_with('myvm', connection='myconnection', username='user', password='secret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.stop": stop_mock, + }, + ): + self.assertDictEqual( + virt.powered_off( + "myvm", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + stop_mock.assert_called_with( + "myvm", connection="myconnection", username="user", password="secret" + ) # Case where an error occurred during the poweroff - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'running'}), - 'virt.stop': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, - 'result': False, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.powered_off('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "running"}), + "virt.stop": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update( + { + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "result": False, + "comment": "No changes had happened", + } + ) + self.assertDictEqual(virt.powered_off("myvm"), ret) # Case there the domain doesn't exist - with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member - ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.powered_off('myvm'), ret) + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): # pylint: disable=no-member + ret.update( + {"changes": {}, "result": False, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.powered_off("myvm"), ret) # Case where the domain is already stopped - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.vm_state': MagicMock(return_value={'myvm': 'shutdown'}) - }): - ret.update({'changes': {}, - 'result': True, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.powered_off('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.vm_state": MagicMock(return_value={"myvm": "shutdown"}), + }, + ): + ret.update( + {"changes": {}, "result": True, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.powered_off("myvm"), ret) def test_snapshot(self): - ''' + """ snapshot state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True} + """ + ret = {"name": "myvm", "changes": {}, "result": True} snapshot_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.snapshot': snapshot_mock - }): - ret.update({'changes': { - 'saved': [{'domain': 'myvm', 'snapshot': True}] - }, - 'comment': 'Snapshot has been taken'}) - self.assertDictEqual(virt.snapshot('myvm'), ret) - snapshot_mock.assert_called_with('myvm', suffix=None, connection=None, username=None, password=None) - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.snapshot': snapshot_mock, - }): - self.assertDictEqual(virt.snapshot('myvm', - suffix='snap', - connection='myconnection', - username='user', - password='secret'), ret) - snapshot_mock.assert_called_with('myvm', - suffix='snap', - connection='myconnection', - username='user', - password='secret') - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.snapshot': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, - 'result': False, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.snapshot('myvm'), ret) - - with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member - ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.snapshot('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.snapshot": snapshot_mock, + }, + ): + ret.update( + { + "changes": {"saved": [{"domain": "myvm", "snapshot": True}]}, + "comment": "Snapshot has been taken", + } + ) + self.assertDictEqual(virt.snapshot("myvm"), ret) + snapshot_mock.assert_called_with( + "myvm", suffix=None, connection=None, username=None, password=None + ) + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.snapshot": snapshot_mock, + }, + ): + self.assertDictEqual( + virt.snapshot( + "myvm", + suffix="snap", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + snapshot_mock.assert_called_with( + "myvm", + suffix="snap", + connection="myconnection", + username="user", + password="secret", + ) + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.snapshot": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update( + { + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "result": False, + "comment": "No changes had happened", + } + ) + self.assertDictEqual(virt.snapshot("myvm"), ret) + + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): # pylint: disable=no-member + ret.update( + {"changes": {}, "result": False, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.snapshot("myvm"), ret) def test_rebooted(self): - ''' + """ rebooted state test cases. - ''' - ret = {'name': 'myvm', - 'changes': {}, - 'result': True} + """ + ret = {"name": "myvm", "changes": {}, "result": True} reboot_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.reboot': reboot_mock - }): - ret.update({'changes': { - 'rebooted': [{'domain': 'myvm', 'reboot': True}] - }, - 'comment': 'Machine has been rebooted'}) - self.assertDictEqual(virt.rebooted('myvm'), ret) - reboot_mock.assert_called_with('myvm', connection=None, username=None, password=None) - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.reboot': reboot_mock, - }): - self.assertDictEqual(virt.rebooted('myvm', - connection='myconnection', - username='user', - password='secret'), ret) - reboot_mock.assert_called_with('myvm', - connection='myconnection', - username='user', - password='secret') - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']), - 'virt.reboot': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {'ignored': [{'domain': 'myvm', 'issue': 'Some error'}]}, - 'result': False, - 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.rebooted('myvm'), ret) - - with patch.dict(virt.__salt__, {'virt.list_domains': MagicMock(return_value=[])}): # pylint: disable=no-member - ret.update({'changes': {}, 'result': False, 'comment': 'No changes had happened'}) - self.assertDictEqual(virt.rebooted('myvm'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.reboot": reboot_mock, + }, + ): + ret.update( + { + "changes": {"rebooted": [{"domain": "myvm", "reboot": True}]}, + "comment": "Machine has been rebooted", + } + ) + self.assertDictEqual(virt.rebooted("myvm"), ret) + reboot_mock.assert_called_with( + "myvm", connection=None, username=None, password=None + ) + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.reboot": reboot_mock, + }, + ): + self.assertDictEqual( + virt.rebooted( + "myvm", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + reboot_mock.assert_called_with( + "myvm", connection="myconnection", username="user", password="secret" + ) + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]), + "virt.reboot": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update( + { + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "result": False, + "comment": "No changes had happened", + } + ) + self.assertDictEqual(virt.rebooted("myvm"), ret) + + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): # pylint: disable=no-member + ret.update( + {"changes": {}, "result": False, "comment": "No changes had happened"} + ) + self.assertDictEqual(virt.rebooted("myvm"), ret) def test_network_defined(self): - ''' + """ network_defined state test cases. - ''' - ret = {'name': 'mynet', 'changes': {}, 'result': True, 'comment': ''} - with patch.dict(virt.__opts__, {'test': False}): + """ + ret = {"name": "mynet", "changes": {}, "result": True, "comment": ""} + with patch.dict(virt.__opts__, {"test": False}): define_mock = MagicMock(return_value=True) # Non-existing network case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(side_effect=[{}, {'mynet': {'active': False}}]), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {'mynet': 'Network defined'}, - 'comment': 'Network mynet defined'}) - self.assertDictEqual(virt.network_defined('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - autostart=False, - connection='myconnection', - username='user', - password='secret'), ret) - define_mock.assert_called_with('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - autostart=False, - start=False, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - connection='myconnection', - username='user', - password='secret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + side_effect=[{}, {"mynet": {"active": False}}] + ), + "virt.network_define": define_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network defined"}, + "comment": "Network mynet defined", + } + ) + self.assertDictEqual( + virt.network_defined( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + { + "start": "2001:db8:ca2:1::10", + "end": "2001:db8:ca2::1f", + }, + ], + }, + autostart=False, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + define_mock.assert_called_with( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + autostart=False, + start=False, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"}, + ], + }, + connection="myconnection", + username="user", + password="secret", + ) # Case where there is nothing to be done define_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {}, 'comment': 'Network mynet exists'}) - self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": True}} + ), + "virt.network_define": define_mock, + }, + ): + ret.update({"changes": {}, "comment": "Network mynet exists"}) + self.assertDictEqual( + virt.network_defined("mynet", "br2", "bridge"), ret + ) # Error case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock(return_value={}), + "virt.network_define": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.network_defined("mynet", "br2", "bridge"), ret + ) # Test cases with __opt__['test'] set to True - with patch.dict(virt.__opts__, {'test': True}): - ret.update({'result': None}) + with patch.dict(virt.__opts__, {"test": True}): + ret.update({"result": None}) # Non-existing network case define_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {'mynet': 'Network defined'}, - 'comment': 'Network mynet defined'}) - self.assertDictEqual(virt.network_defined('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - autostart=False, - connection='myconnection', - username='user', - password='secret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock(return_value={}), + "virt.network_define": define_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network defined"}, + "comment": "Network mynet defined", + } + ) + self.assertDictEqual( + virt.network_defined( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + { + "start": "2001:db8:ca2:1::10", + "end": "2001:db8:ca2::1f", + }, + ], + }, + autostart=False, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) define_mock.assert_not_called() # Case where there is nothing to be done define_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {}, 'comment': 'Network mynet exists', 'result': True}) - self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": True}} + ), + "virt.network_define": define_mock, + }, + ): + ret.update( + {"changes": {}, "comment": "Network mynet exists", "result": True} + ) + self.assertDictEqual( + virt.network_defined("mynet", "br2", "bridge"), ret + ) # Error case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.network_defined('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ) + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.network_defined("mynet", "br2", "bridge"), ret + ) def test_network_running(self): - ''' + """ network_running state test cases. - ''' - ret = {'name': 'mynet', 'changes': {}, 'result': True, 'comment': ''} - with patch.dict(virt.__opts__, {'test': False}): + """ + ret = {"name": "mynet", "changes": {}, "result": True, "comment": ""} + with patch.dict(virt.__opts__, {"test": False}): define_mock = MagicMock(return_value=True) start_mock = MagicMock(return_value=True) # Non-existing network case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(side_effect=[{}, {'mynet': {'active': False}}]), - 'virt.network_define': define_mock, - 'virt.network_start': start_mock, - }): - ret.update({'changes': {'mynet': 'Network defined and started'}, - 'comment': 'Network mynet defined and started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - autostart=False, - connection='myconnection', - username='user', - password='secret'), ret) - define_mock.assert_called_with('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - autostart=False, - start=False, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - connection='myconnection', - username='user', - password='secret') - start_mock.assert_called_with('mynet', - connection='myconnection', - username='user', - password='secret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + side_effect=[{}, {"mynet": {"active": False}}] + ), + "virt.network_define": define_mock, + "virt.network_start": start_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network defined and started"}, + "comment": "Network mynet defined and started", + } + ) + self.assertDictEqual( + virt.network_running( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + { + "start": "2001:db8:ca2:1::10", + "end": "2001:db8:ca2::1f", + }, + ], + }, + autostart=False, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + define_mock.assert_called_with( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + autostart=False, + start=False, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"}, + ], + }, + connection="myconnection", + username="user", + password="secret", + ) + start_mock.assert_called_with( + "mynet", + connection="myconnection", + username="user", + password="secret", + ) # Case where there is nothing to be done define_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {}, 'comment': 'Network mynet exists and is running'}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": True}} + ), + "virt.network_define": define_mock, + }, + ): + ret.update( + {"changes": {}, "comment": "Network mynet exists and is running"} + ) + self.assertDictEqual( + virt.network_running("mynet", "br2", "bridge"), ret + ) # Network existing and stopped case start_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': False}}), - 'virt.network_start': start_mock, - 'virt.network_define': define_mock, - }): - ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet exists and started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', - connection='myconnection', - username='user', - password='secret'), ret) - start_mock.assert_called_with('mynet', connection='myconnection', username='user', password='secret') + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": False}} + ), + "virt.network_start": start_mock, + "virt.network_define": define_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network started"}, + "comment": "Network mynet exists and started", + } + ) + self.assertDictEqual( + virt.network_running( + "mynet", + "br2", + "bridge", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + start_mock.assert_called_with( + "mynet", + connection="myconnection", + username="user", + password="secret", + ) # Error case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock(return_value={}), + "virt.network_define": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.network_running("mynet", "br2", "bridge"), ret + ) # Test cases with __opt__['test'] set to True - with patch.dict(virt.__opts__, {'test': True}): - ret.update({'result': None}) + with patch.dict(virt.__opts__, {"test": True}): + ret.update({"result": None}) # Non-existing network case define_mock.reset_mock() start_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={}), - 'virt.network_define': define_mock, - 'virt.network_start': start_mock, - }): - ret.update({'changes': {'mynet': 'Network defined and started'}, - 'comment': 'Network mynet defined and started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', - vport='openvswitch', - tag=180, - ipv4_config={ - 'cidr': '192.168.2.0/24', - 'dhcp_ranges': [ - {'start': '192.168.2.10', 'end': '192.168.2.25'}, - {'start': '192.168.2.110', 'end': '192.168.2.125'}, - ] - }, - ipv6_config={ - 'cidr': '2001:db8:ca2:2::1/64', - 'dhcp_ranges': [ - {'start': '2001:db8:ca2:1::10', 'end': '2001:db8:ca2::1f'}, - ] - }, - autostart=False, - connection='myconnection', - username='user', - password='secret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock(return_value={}), + "virt.network_define": define_mock, + "virt.network_start": start_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network defined and started"}, + "comment": "Network mynet defined and started", + } + ) + self.assertDictEqual( + virt.network_running( + "mynet", + "br2", + "bridge", + vport="openvswitch", + tag=180, + ipv4_config={ + "cidr": "192.168.2.0/24", + "dhcp_ranges": [ + {"start": "192.168.2.10", "end": "192.168.2.25"}, + {"start": "192.168.2.110", "end": "192.168.2.125"}, + ], + }, + ipv6_config={ + "cidr": "2001:db8:ca2:2::1/64", + "dhcp_ranges": [ + { + "start": "2001:db8:ca2:1::10", + "end": "2001:db8:ca2::1f", + }, + ], + }, + autostart=False, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) define_mock.assert_not_called() start_mock.assert_not_called() # Case where there is nothing to be done define_mock.reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': True}}), - 'virt.network_define': define_mock, - }): - ret.update({'changes': {}, 'comment': 'Network mynet exists and is running'}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": True}} + ), + "virt.network_define": define_mock, + }, + ): + ret.update( + {"changes": {}, "comment": "Network mynet exists and is running"} + ) + self.assertDictEqual( + virt.network_running("mynet", "br2", "bridge"), ret + ) # Network existing and stopped case start_mock = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(return_value={'mynet': {'active': False}}), - 'virt.network_start': start_mock, - 'virt.network_define': define_mock, - }): - ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet exists and started'}) - self.assertDictEqual(virt.network_running('mynet', - 'br2', - 'bridge', - connection='myconnection', - username='user', - password='secret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + return_value={"mynet": {"active": False}} + ), + "virt.network_start": start_mock, + "virt.network_define": define_mock, + }, + ): + ret.update( + { + "changes": {"mynet": "Network started"}, + "comment": "Network mynet exists and started", + } + ) + self.assertDictEqual( + virt.network_running( + "mynet", + "br2", + "bridge", + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) start_mock.assert_not_called() # Error case - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.network_info': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.network_running('mynet', 'br2', 'bridge'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.network_info": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ) + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.network_running("mynet", "br2", "bridge"), ret + ) def test_pool_defined(self): - ''' + """ pool_defined state test cases. - ''' - ret = {'name': 'mypool', 'changes': {}, 'result': True, 'comment': ''} - mocks = {mock: MagicMock(return_value=True) for mock in ['define', 'autostart', 'build']} - with patch.dict(virt.__opts__, {'test': False}): - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(side_effect=[{}, {'mypool': {'state': 'stopped', 'autostart': True}}]), - 'virt.pool_define': mocks['define'], - 'virt.pool_build': mocks['build'], - 'virt.pool_set_autostart': mocks['autostart'] - }): - ret.update({'changes': {'mypool': 'Pool defined, marked for autostart'}, - 'comment': 'Pool mypool defined, marked for autostart'}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}, - transient=True, - autostart=True, - connection='myconnection', - username='user', - password='secret'), ret) - mocks['define'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - transient=True, - start=False, - connection='myconnection', - username='user', - password='secret') - mocks['autostart'].assert_called_with('mypool', - state='on', - connection='myconnection', - username='user', - password='secret') - mocks['build'].assert_called_with('mypool', - connection='myconnection', - username='user', - password='secret') - - mocks['update'] = MagicMock(return_value=False) + """ + ret = {"name": "mypool", "changes": {}, "result": True, "comment": ""} + mocks = { + mock: MagicMock(return_value=True) + for mock in ["define", "autostart", "build"] + } + with patch.dict(virt.__opts__, {"test": False}): + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + side_effect=[ + {}, + {"mypool": {"state": "stopped", "autostart": True}}, + ] + ), + "virt.pool_define": mocks["define"], + "virt.pool_build": mocks["build"], + "virt.pool_set_autostart": mocks["autostart"], + }, + ): + ret.update( + { + "changes": {"mypool": "Pool defined, marked for autostart"}, + "comment": "Pool mypool defined, marked for autostart", + } + ) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + transient=True, + autostart=True, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + mocks["define"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + transient=True, + start=False, + connection="myconnection", + username="user", + password="secret", + ) + mocks["autostart"].assert_called_with( + "mypool", + state="on", + connection="myconnection", + username="user", + password="secret", + ) + mocks["build"].assert_called_with( + "mypool", + connection="myconnection", + username="user", + password="secret", + ) + + mocks["update"] = MagicMock(return_value=False) for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_build': mocks['build'], - }): - ret.update({'changes': {}, 'comment': 'Pool mypool unchanged'}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['build'].assert_not_called() - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={}), - 'virt.pool_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "stopped", "autostart": True}} + ), + "virt.pool_update": mocks["update"], + "virt.pool_build": mocks["build"], + }, + ): + ret.update({"changes": {}, "comment": "Pool mypool unchanged"}) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["build"].assert_not_called() + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock(return_value={}), + "virt.pool_define": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) # Test case with update and autostart change on stopped pool for mock in mocks: mocks[mock].reset_mock() - mocks['update'] = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_set_autostart': mocks['autostart'], - 'virt.pool_build': mocks['build'], - }): - ret.update({'changes': {'mypool': 'Pool updated, built, autostart flag changed'}, - 'comment': 'Pool mypool updated, built, autostart flag changed', - 'result': True}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - autostart=False, - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['build'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['autostart'].assert_called_with('mypool', state='off', - connection=None, username=None, password=None) - mocks['update'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - connection=None, - username=None, - password=None) + mocks["update"] = MagicMock(return_value=True) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "stopped", "autostart": True}} + ), + "virt.pool_update": mocks["update"], + "virt.pool_set_autostart": mocks["autostart"], + "virt.pool_build": mocks["build"], + }, + ): + ret.update( + { + "changes": { + "mypool": "Pool updated, built, autostart flag changed" + }, + "comment": "Pool mypool updated, built, autostart flag changed", + "result": True, + } + ) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + autostart=False, + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["build"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["autostart"].assert_called_with( + "mypool", state="off", connection=None, username=None, password=None + ) + mocks["update"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None, + ) # test case with update and no autostart change on running pool for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': False}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_build': mocks['build'], - }): - ret.update({'changes': {'mypool': 'Pool updated'}, - 'comment': 'Pool mypool updated', - 'result': True}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - autostart=False, - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['update'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - connection=None, - username=None, - password=None) - - with patch.dict(virt.__opts__, {'test': True}): + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={ + "mypool": {"state": "running", "autostart": False} + } + ), + "virt.pool_update": mocks["update"], + "virt.pool_build": mocks["build"], + }, + ): + ret.update( + { + "changes": {"mypool": "Pool updated"}, + "comment": "Pool mypool updated", + "result": True, + } + ) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + autostart=False, + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["update"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None, + ) + + with patch.dict(virt.__opts__, {"test": True}): # test case with test=True and no change - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), - 'virt.pool_update': MagicMock(return_value=False), - }): - ret.update({'changes': {}, 'comment': 'Pool mypool unchanged', - 'result': True}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "running", "autostart": True}} + ), + "virt.pool_update": MagicMock(return_value=False), + }, + ): + ret.update( + {"changes": {}, "comment": "Pool mypool unchanged", "result": True} + ) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) # test case with test=True and pool to be defined for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={}), - }): - ret.update({'changes': {'mypool': 'Pool defined, marked for autostart'}, - 'comment': 'Pool mypool defined, marked for autostart', - 'result': None}) - self.assertDictEqual(virt.pool_defined('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}, - transient=True, - autostart=True, - connection='myconnection', - username='user', - password='secret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock(return_value={}), + }, + ): + ret.update( + { + "changes": {"mypool": "Pool defined, marked for autostart"}, + "comment": "Pool mypool defined, marked for autostart", + "result": None, + } + ) + self.assertDictEqual( + virt.pool_defined( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + transient=True, + autostart=True, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) def test_pool_running(self): - ''' + """ pool_running state test cases. - ''' - ret = {'name': 'mypool', 'changes': {}, 'result': True, 'comment': ''} - mocks = {mock: MagicMock(return_value=True) for mock in ['define', 'autostart', 'build', 'start', 'stop']} - with patch.dict(virt.__opts__, {'test': False}): - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(side_effect=[{}, {'mypool': {'state': 'stopped', 'autostart': True}}]), - 'virt.pool_define': mocks['define'], - 'virt.pool_build': mocks['build'], - 'virt.pool_start': mocks['start'], - 'virt.pool_set_autostart': mocks['autostart'] - }): - ret.update({'changes': {'mypool': 'Pool defined, marked for autostart, started'}, - 'comment': 'Pool mypool defined, marked for autostart, started'}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}, - transient=True, - autostart=True, - connection='myconnection', - username='user', - password='secret'), ret) - mocks['define'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - transient=True, - start=False, - connection='myconnection', - username='user', - password='secret') - mocks['autostart'].assert_called_with('mypool', - state='on', - connection='myconnection', - username='user', - password='secret') - mocks['build'].assert_called_with('mypool', - connection='myconnection', - username='user', - password='secret') - mocks['start'].assert_called_with('mypool', - connection='myconnection', - username='user', - password='secret') - - mocks['update'] = MagicMock(return_value=False) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), - 'virt.pool_update': MagicMock(return_value=False), - }): - ret.update({'changes': {}, 'comment': 'Pool mypool already running'}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + """ + ret = {"name": "mypool", "changes": {}, "result": True, "comment": ""} + mocks = { + mock: MagicMock(return_value=True) + for mock in ["define", "autostart", "build", "start", "stop"] + } + with patch.dict(virt.__opts__, {"test": False}): + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + side_effect=[ + {}, + {"mypool": {"state": "stopped", "autostart": True}}, + ] + ), + "virt.pool_define": mocks["define"], + "virt.pool_build": mocks["build"], + "virt.pool_start": mocks["start"], + "virt.pool_set_autostart": mocks["autostart"], + }, + ): + ret.update( + { + "changes": { + "mypool": "Pool defined, marked for autostart, started" + }, + "comment": "Pool mypool defined, marked for autostart, started", + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + transient=True, + autostart=True, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) + mocks["define"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + transient=True, + start=False, + connection="myconnection", + username="user", + password="secret", + ) + mocks["autostart"].assert_called_with( + "mypool", + state="on", + connection="myconnection", + username="user", + password="secret", + ) + mocks["build"].assert_called_with( + "mypool", + connection="myconnection", + username="user", + password="secret", + ) + mocks["start"].assert_called_with( + "mypool", + connection="myconnection", + username="user", + password="secret", + ) + + mocks["update"] = MagicMock(return_value=False) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "running", "autostart": True}} + ), + "virt.pool_update": MagicMock(return_value=False), + }, + ): + ret.update({"changes": {}, "comment": "Pool mypool already running"}) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_build': mocks['build'], - 'virt.pool_start': mocks['start'] - }): - ret.update({'changes': {'mypool': 'Pool started'}, 'comment': 'Pool mypool started'}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['start'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['build'].assert_not_called() - - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={}), - 'virt.pool_define': MagicMock(side_effect=self.mock_libvirt.libvirtError('Some error')) - }): - ret.update({'changes': {}, 'comment': 'Some error', 'result': False}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "stopped", "autostart": True}} + ), + "virt.pool_update": mocks["update"], + "virt.pool_build": mocks["build"], + "virt.pool_start": mocks["start"], + }, + ): + ret.update( + { + "changes": {"mypool": "Pool started"}, + "comment": "Pool mypool started", + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["start"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["build"].assert_not_called() + + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock(return_value={}), + "virt.pool_define": MagicMock( + side_effect=self.mock_libvirt.libvirtError("Some error") + ), + }, + ): + ret.update({"changes": {}, "comment": "Some error", "result": False}) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) # Test case with update and autostart change on stopped pool for mock in mocks: mocks[mock].reset_mock() - mocks['update'] = MagicMock(return_value=True) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_set_autostart': mocks['autostart'], - 'virt.pool_build': mocks['build'], - 'virt.pool_start': mocks['start'] - }): - ret.update({'changes': {'mypool': 'Pool updated, built, autostart flag changed, started'}, - 'comment': 'Pool mypool updated, built, autostart flag changed, started', - 'result': True}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - autostart=False, - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['start'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['build'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['autostart'].assert_called_with('mypool', state='off', - connection=None, username=None, password=None) - mocks['update'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - connection=None, - username=None, - password=None) + mocks["update"] = MagicMock(return_value=True) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "stopped", "autostart": True}} + ), + "virt.pool_update": mocks["update"], + "virt.pool_set_autostart": mocks["autostart"], + "virt.pool_build": mocks["build"], + "virt.pool_start": mocks["start"], + }, + ): + ret.update( + { + "changes": { + "mypool": "Pool updated, built, autostart flag changed, started" + }, + "comment": "Pool mypool updated, built, autostart flag changed, started", + "result": True, + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + autostart=False, + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["start"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["build"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["autostart"].assert_called_with( + "mypool", state="off", connection=None, username=None, password=None + ) + mocks["update"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None, + ) # test case with update and no autostart change on running pool for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': False}}), - 'virt.pool_update': mocks['update'], - 'virt.pool_build': mocks['build'], - 'virt.pool_start': mocks['start'], - 'virt.pool_stop': mocks['stop'] - }): - ret.update({'changes': {'mypool': 'Pool updated, built, restarted'}, - 'comment': 'Pool mypool updated, built, restarted', - 'result': True}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - autostart=False, - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}), ret) - mocks['stop'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['start'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['build'].assert_called_with('mypool', connection=None, username=None, password=None) - mocks['update'].assert_called_with('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source_devices=[{'path': '/dev/sda'}], - source_dir=None, - source_adapter=None, - source_hosts=None, - source_auth=None, - source_name=None, - source_format=None, - source_initiator=None, - connection=None, - username=None, - password=None) - - with patch.dict(virt.__opts__, {'test': True}): + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={ + "mypool": {"state": "running", "autostart": False} + } + ), + "virt.pool_update": mocks["update"], + "virt.pool_build": mocks["build"], + "virt.pool_start": mocks["start"], + "virt.pool_stop": mocks["stop"], + }, + ): + ret.update( + { + "changes": {"mypool": "Pool updated, built, restarted"}, + "comment": "Pool mypool updated, built, restarted", + "result": True, + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + autostart=False, + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) + mocks["stop"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["start"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["build"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) + mocks["update"].assert_called_with( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source_devices=[{"path": "/dev/sda"}], + source_dir=None, + source_adapter=None, + source_hosts=None, + source_auth=None, + source_name=None, + source_format=None, + source_initiator=None, + connection=None, + username=None, + password=None, + ) + + with patch.dict(virt.__opts__, {"test": True}): # test case with test=True and no change - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'running', 'autostart': True}}), - 'virt.pool_update': MagicMock(return_value=False), - }): - ret.update({'changes': {}, 'comment': 'Pool mypool already running', - 'result': True}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "running", "autostart": True}} + ), + "virt.pool_update": MagicMock(return_value=False), + }, + ): + ret.update( + { + "changes": {}, + "comment": "Pool mypool already running", + "result": True, + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) # test case with test=True and started for mock in mocks: mocks[mock].reset_mock() - mocks['update'] = MagicMock(return_value=False) - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={'mypool': {'state': 'stopped', 'autostart': True}}), - 'virt.pool_update': mocks['update'] - }): - ret.update({'changes': {'mypool': 'Pool started'}, - 'comment': 'Pool mypool started', - 'result': None}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - source={'devices': [{'path': '/dev/sda'}]}), ret) + mocks["update"] = MagicMock(return_value=False) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock( + return_value={"mypool": {"state": "stopped", "autostart": True}} + ), + "virt.pool_update": mocks["update"], + }, + ): + ret.update( + { + "changes": {"mypool": "Pool started"}, + "comment": "Pool mypool started", + "result": None, + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + source={"devices": [{"path": "/dev/sda"}]}, + ), + ret, + ) # test case with test=True and pool to be defined for mock in mocks: mocks[mock].reset_mock() - with patch.dict(virt.__salt__, { # pylint: disable=no-member - 'virt.pool_info': MagicMock(return_value={}), - }): - ret.update({'changes': {'mypool': 'Pool defined, marked for autostart, started'}, - 'comment': 'Pool mypool defined, marked for autostart, started', - 'result': None}) - self.assertDictEqual(virt.pool_running('mypool', - ptype='logical', - target='/dev/base', - permissions={'mode': '0770', - 'owner': 1000, - 'group': 100, - 'label': 'seclabel'}, - source={'devices': [{'path': '/dev/sda'}]}, - transient=True, - autostart=True, - connection='myconnection', - username='user', - password='secret'), ret) + with patch.dict( + virt.__salt__, + { # pylint: disable=no-member + "virt.pool_info": MagicMock(return_value={}), + }, + ): + ret.update( + { + "changes": { + "mypool": "Pool defined, marked for autostart, started" + }, + "comment": "Pool mypool defined, marked for autostart, started", + "result": None, + } + ) + self.assertDictEqual( + virt.pool_running( + "mypool", + ptype="logical", + target="/dev/base", + permissions={ + "mode": "0770", + "owner": 1000, + "group": 100, + "label": "seclabel", + }, + source={"devices": [{"path": "/dev/sda"}]}, + transient=True, + autostart=True, + connection="myconnection", + username="user", + password="secret", + ), + ret, + ) def test_pool_deleted(self): - ''' + """ Test the pool_deleted state - ''' + """ # purge=False test case, stopped pool - with patch.dict(virt.__salt__, { - 'virt.pool_info': MagicMock(return_value={'test01': {'state': 'stopped', 'type': 'dir'}}), - 'virt.pool_undefine': MagicMock(return_value=True) - }): + with patch.dict( + virt.__salt__, + { + "virt.pool_info": MagicMock( + return_value={"test01": {"state": "stopped", "type": "dir"}} + ), + "virt.pool_undefine": MagicMock(return_value=True), + }, + ): expected = { - 'name': 'test01', - 'changes': { - 'stopped': False, - 'deleted_volumes': [], - 'deleted': False, - 'undefined': True, - }, - 'result': True, - 'comment': '', + "name": "test01", + "changes": { + "stopped": False, + "deleted_volumes": [], + "deleted": False, + "undefined": True, + }, + "result": True, + "comment": "", } - with patch.dict(virt.__opts__, {'test': False}): - self.assertDictEqual(expected, virt.pool_deleted('test01')) + with patch.dict(virt.__opts__, {"test": False}): + self.assertDictEqual(expected, virt.pool_deleted("test01")) - with patch.dict(virt.__opts__, {'test': True}): - expected['result'] = None - self.assertDictEqual(expected, virt.pool_deleted('test01')) + with patch.dict(virt.__opts__, {"test": True}): + expected["result"] = None + self.assertDictEqual(expected, virt.pool_deleted("test01")) # purge=False test case - with patch.dict(virt.__salt__, { - 'virt.pool_info': MagicMock(return_value={'test01': {'state': 'running', 'type': 'dir'}}), - 'virt.pool_undefine': MagicMock(return_value=True), - 'virt.pool_stop': MagicMock(return_value=True) - }): + with patch.dict( + virt.__salt__, + { + "virt.pool_info": MagicMock( + return_value={"test01": {"state": "running", "type": "dir"}} + ), + "virt.pool_undefine": MagicMock(return_value=True), + "virt.pool_stop": MagicMock(return_value=True), + }, + ): expected = { - 'name': 'test01', - 'changes': { - 'stopped': True, - 'deleted_volumes': [], - 'deleted': False, - 'undefined': True, - }, - 'result': True, - 'comment': '', + "name": "test01", + "changes": { + "stopped": True, + "deleted_volumes": [], + "deleted": False, + "undefined": True, + }, + "result": True, + "comment": "", } - with patch.dict(virt.__opts__, {'test': False}): - self.assertDictEqual(expected, virt.pool_deleted('test01')) + with patch.dict(virt.__opts__, {"test": False}): + self.assertDictEqual(expected, virt.pool_deleted("test01")) - with patch.dict(virt.__opts__, {'test': True}): - expected['result'] = None - self.assertDictEqual(expected, virt.pool_deleted('test01')) + with patch.dict(virt.__opts__, {"test": True}): + expected["result"] = None + self.assertDictEqual(expected, virt.pool_deleted("test01")) # purge=True test case - with patch.dict(virt.__salt__, { - 'virt.pool_info': MagicMock(return_value={'test01': {'state': 'running', 'type': 'dir'}}), - 'virt.pool_list_volumes': MagicMock(return_value=['vm01.qcow2', 'vm02.qcow2']), - 'virt.pool_refresh': MagicMock(return_value=True), - 'virt.volume_delete': MagicMock(return_value=True), - 'virt.pool_stop': MagicMock(return_value=True), - 'virt.pool_delete': MagicMock(return_value=True), - 'virt.pool_undefine': MagicMock(return_value=True) - }): + with patch.dict( + virt.__salt__, + { + "virt.pool_info": MagicMock( + return_value={"test01": {"state": "running", "type": "dir"}} + ), + "virt.pool_list_volumes": MagicMock( + return_value=["vm01.qcow2", "vm02.qcow2"] + ), + "virt.pool_refresh": MagicMock(return_value=True), + "virt.volume_delete": MagicMock(return_value=True), + "virt.pool_stop": MagicMock(return_value=True), + "virt.pool_delete": MagicMock(return_value=True), + "virt.pool_undefine": MagicMock(return_value=True), + }, + ): expected = { - 'name': 'test01', - 'changes': { - 'stopped': True, - 'deleted_volumes': ['vm01.qcow2', 'vm02.qcow2'], - 'deleted': True, - 'undefined': True, - }, - 'result': True, - 'comment': '', + "name": "test01", + "changes": { + "stopped": True, + "deleted_volumes": ["vm01.qcow2", "vm02.qcow2"], + "deleted": True, + "undefined": True, + }, + "result": True, + "comment": "", } - with patch.dict(virt.__opts__, {'test': False}): - self.assertDictEqual(expected, virt.pool_deleted('test01', purge=True)) + with patch.dict(virt.__opts__, {"test": False}): + self.assertDictEqual(expected, virt.pool_deleted("test01", purge=True)) - with patch.dict(virt.__opts__, {'test': True}): - expected['result'] = None - self.assertDictEqual(expected, virt.pool_deleted('test01', purge=True)) + with patch.dict(virt.__opts__, {"test": True}): + expected["result"] = None + self.assertDictEqual(expected, virt.pool_deleted("test01", purge=True)) # Case of backend not unsupporting delete operations - with patch.dict(virt.__salt__, { - 'virt.pool_info': MagicMock(return_value={'test01': {'state': 'running', 'type': 'iscsi'}}), - 'virt.pool_stop': MagicMock(return_value=True), - 'virt.pool_undefine': MagicMock(return_value=True) - }): + with patch.dict( + virt.__salt__, + { + "virt.pool_info": MagicMock( + return_value={"test01": {"state": "running", "type": "iscsi"}} + ), + "virt.pool_stop": MagicMock(return_value=True), + "virt.pool_undefine": MagicMock(return_value=True), + }, + ): expected = { - 'name': 'test01', - 'changes': { - 'stopped': True, - 'deleted_volumes': [], - 'deleted': False, - 'undefined': True, - }, - 'result': True, - 'comment': 'Unsupported actions for pool of type "iscsi": deleting volume, deleting pool', + "name": "test01", + "changes": { + "stopped": True, + "deleted_volumes": [], + "deleted": False, + "undefined": True, + }, + "result": True, + "comment": 'Unsupported actions for pool of type "iscsi": deleting volume, deleting pool', } - with patch.dict(virt.__opts__, {'test': False}): - self.assertDictEqual(expected, virt.pool_deleted('test01', purge=True)) + with patch.dict(virt.__opts__, {"test": False}): + self.assertDictEqual(expected, virt.pool_deleted("test01", purge=True)) + + with patch.dict(virt.__opts__, {"test": True}): + expected["result"] = None + self.assertDictEqual(expected, virt.pool_deleted("test01", purge=True)) + + def test_volume_defined(self): + """ + test the virt.volume_defined state + """ + with patch.dict(virt.__opts__, {"test": False}): + # test case: creating a volume + define_mock = MagicMock() + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock(return_value={"mypool": {}}), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {"mypool/myvol": {"old": "", "new": "defined"}}, + "result": True, + "comment": "Volume myvol defined in pool mypool", + }, + ) + define_mock.assert_called_once_with( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ) + + # test case: with existing volume + define_mock.reset_mock() + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "1293942784", + "backing_store": { + "path": "/path/to/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": True, + "comment": "volume is existing", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different sizes + define_mock.reset_mock() + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "12345", + "backing_store": { + "path": "/path/to/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": True, + "comment": "The capacity of the volume is different, but no resize performed", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different backing store + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "1234", + "backing_store": { + "path": "/path/to/other/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different format + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "raw", + "capacity": "1234", + "backing_store": { + "path": "/path/to/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() - with patch.dict(virt.__opts__, {'test': True}): - expected['result'] = None - self.assertDictEqual(expected, virt.pool_deleted('test01', purge=True)) + # test case: no pool + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "1234", + "backing_store": { + "path": "/path/to/other/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different format + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=[]), + "virt.volume_infos": MagicMock(return_value={}), + "virt.volume_define": define_mock, + }, + ): + self.assertRaisesRegex( + SaltInvocationError, + "Storage pool mypool not existing", + virt.volume_defined, + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ) + + # Test mode cases + with patch.dict(virt.__opts__, {"test": True}): + # test case: creating a volume + define_mock.reset_mock() + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock(return_value={"mypool": {}}), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {"mypool/myvol": {"old": "", "new": "defined"}}, + "result": None, + "comment": "Volume myvol would be defined in pool mypool", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different sizes + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "12345", + "backing_store": { + "path": "/path/to/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": True, + "comment": "The capacity of the volume is different, but no resize performed", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different backing store + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "1234", + "backing_store": { + "path": "/path/to/other/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different format + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "raw", + "capacity": "1234", + "backing_store": { + "path": "/path/to/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() + + # test case: no pool + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=["mypool"]), + "virt.volume_infos": MagicMock( + return_value={ + "mypool": { + "myvol": { + "format": "qcow2", + "capacity": "1234", + "backing_store": { + "path": "/path/to/other/image", + "format": "raw", + }, + } + } + } + ), + "virt.volume_define": define_mock, + }, + ): + self.assertDictEqual( + virt.volume_defined( + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ), + { + "name": "myvol", + "changes": {}, + "result": False, + "comment": "A volume with the same name but different backing store or format is existing", + }, + ) + define_mock.assert_not_called() + + # test case: with existing volume, different format + with patch.dict( + virt.__salt__, + { + "virt.list_pools": MagicMock(return_value=[]), + "virt.volume_infos": MagicMock(return_value={}), + "virt.volume_define": define_mock, + }, + ): + self.assertRaisesRegex( + SaltInvocationError, + "Storage pool mypool not existing", + virt.volume_defined, + "mypool", + "myvol", + "1234", + allocation="12345", + format="qcow2", + type="file", + permissions={"mode": "0755", "owner": "123", "group": "456"}, + backing_store={"path": "/path/to/image", "format": "raw"}, + nocow=True, + connection="test:///", + username="jdoe", + password="supersecret", + ) -- 2.23.0
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor