65038f31ef
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=173
21135 lines
840 KiB
Diff
21135 lines
840 KiB
Diff
From de6c0e021f366ac7e0c4f6f103ece6624ed71a77 Mon Sep 17 00:00:00 2001
|
|
From: Cedric Bosdonnat <cbosdonnat@suse.com>
|
|
Date: Tue, 1 Sep 2020 10:38:39 +0200
|
|
Subject: [PATCH] Opensuse 3000.2 virt backports (#236) (#257)
|
|
|
|
* 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>
|
|
|
|
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 | 4761 ++++++++++--------
|
|
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(+), 5918 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 1652f3fca1..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,31 +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
|
|
-
|
|
- # pylint: disable=no-name-in-module
|
|
- from libvirt import libvirtError
|
|
-
|
|
- # pylint: enable=no-name-in-module
|
|
-
|
|
- HAS_LIBVIRT = True
|
|
-except ImportError:
|
|
- HAS_LIBVIRT = False
|
|
|
|
# Import salt libs
|
|
import salt.utils.files
|
|
@@ -111,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
|
|
@@ -165,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.
|
|
|
|
@@ -187,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:
|
|
@@ -288,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:
|
|
@@ -341,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:
|
|
@@ -354,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:
|
|
@@ -368,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:
|
|
@@ -382,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 key, value in six.iteritems(g_node.attrib):
|
|
+ out[key] = value
|
|
+ return out
|
|
+
|
|
+
|
|
+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('devices/graphics'):
|
|
+ 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(dom):
|
|
- '''
|
|
+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:
|
|
@@ -844,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
|
|
|
|
@@ -907,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 = {}
|
|
|
|
@@ -930,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
|
|
@@ -1025,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)
|
|
@@ -1139,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
|
|
|
|
|
|
@@ -1173,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.
|
|
@@ -1255,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.
|
|
@@ -1281,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'``)
|
|
@@ -1342,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:
|
|
|
|
@@ -1398,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)
|
|
@@ -1409,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
|
|
@@ -1467,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')
|
|
-
|
|
- 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'}
|
|
+ else:
|
|
+ # Unknown hypervisor
|
|
+ raise SaltInvocationError(
|
|
+ "Unsupported hypervisor when handling disk image: {0}".format(
|
|
+ virt_hypervisor
|
|
+ )
|
|
+ )
|
|
|
|
- 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]
|
|
+ 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 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()
|
|
|
|
@@ -1630,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
|
|
@@ -1698,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
|
|
@@ -1825,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
|
|
|
|
@@ -1871,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)
|
|
@@ -1883,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
|
|
@@ -1944,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
|
|
@@ -1998,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
|
|
@@ -2057,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):
|
|
@@ -2067,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
|
|
@@ -2085,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):
|
|
@@ -2095,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
|
|
@@ -2113,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):
|
|
@@ -2123,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:
|
|
|
|
@@ -2158,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
|
|
@@ -2211,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_:
|
|
@@ -2232,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
|
|
@@ -2266,7 +2797,7 @@ def node_info(**kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' virt.node_info
|
|
- '''
|
|
+ """
|
|
conn = __get_conn(**kwargs)
|
|
info = _node_info(conn)
|
|
conn.close()
|
|
@@ -2274,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
|
|
@@ -2293,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()
|
|
@@ -2301,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
|
|
@@ -2320,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
|
|
@@ -2345,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
|
|
@@ -2372,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.
|
|
|
|
@@ -2403,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
|
|
@@ -2427,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.
|
|
|
|
@@ -2452,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
|
|
@@ -2473,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
|
|
@@ -2486,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
|
|
|
|
@@ -2505,7 +3061,7 @@ def freemem(**kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' virt.freemem
|
|
- '''
|
|
+ """
|
|
conn = __get_conn(**kwargs)
|
|
mem = _freemem(conn)
|
|
conn.close()
|
|
@@ -2513,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:
|
|
@@ -2524,7 +3080,7 @@ def _freecpu(conn):
|
|
|
|
|
|
def freecpu(**kwargs):
|
|
- '''
|
|
+ """
|
|
Return an int representing the number of unallocated cpus on this
|
|
hypervisor
|
|
|
|
@@ -2543,7 +3099,7 @@ def freecpu(**kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' virt.freecpu
|
|
- '''
|
|
+ """
|
|
conn = __get_conn(**kwargs)
|
|
cpus = _freecpu(conn)
|
|
conn.close()
|
|
@@ -2551,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
|
|
@@ -2569,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
|
|
@@ -2599,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:
|
|
@@ -2634,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
|
|
@@ -2686,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
|
|
@@ -2695,7 +3257,7 @@ def shutdown(vm_, **kwargs):
|
|
|
|
|
|
def pause(vm_, **kwargs):
|
|
- '''
|
|
+ """
|
|
Pause the named vm
|
|
|
|
:param vm_: domain name
|
|
@@ -2714,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
|
|
@@ -2723,7 +3285,7 @@ def pause(vm_, **kwargs):
|
|
|
|
|
|
def resume(vm_, **kwargs):
|
|
- '''
|
|
+ """
|
|
Resume the named vm
|
|
|
|
:param vm_: domain name
|
|
@@ -2742,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
|
|
@@ -2751,7 +3313,7 @@ def resume(vm_, **kwargs):
|
|
|
|
|
|
def start(name, **kwargs):
|
|
- '''
|
|
+ """
|
|
Start a defined domain
|
|
|
|
:param vm_: domain name
|
|
@@ -2770,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()
|
|
@@ -2778,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
|
|
@@ -2797,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()
|
|
@@ -2805,7 +3367,7 @@ def stop(name, **kwargs):
|
|
|
|
|
|
def reboot(name, **kwargs):
|
|
- '''
|
|
+ """
|
|
Reboot a domain via ACPI request
|
|
|
|
:param vm_: domain name
|
|
@@ -2824,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()
|
|
@@ -2832,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
|
|
@@ -2851,7 +3413,7 @@ def reset(vm_, **kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' virt.reset <domain>
|
|
- '''
|
|
+ """
|
|
conn = __get_conn(**kwargs)
|
|
dom = _get_domain(conn, vm_)
|
|
|
|
@@ -2864,7 +3426,7 @@ def reset(vm_, **kwargs):
|
|
|
|
|
|
def ctrl_alt_del(vm_, **kwargs):
|
|
- '''
|
|
+ """
|
|
Sends CTRL+ALT+DEL to a VM
|
|
|
|
:param vm_: domain name
|
|
@@ -2883,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
|
|
@@ -2892,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
|
|
@@ -2911,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()
|
|
@@ -2919,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
|
|
@@ -2938,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
|
|
@@ -2969,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()
|
|
@@ -2977,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
|
|
@@ -2997,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
|
|
@@ -3030,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
|
|
@@ -3076,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
|
|
@@ -3111,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
|
|
@@ -3145,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
|
|
@@ -3179,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.
|
|
@@ -3204,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.
|
|
|
|
@@ -3252,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()
|
|
@@ -3270,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
|
|
|
|
@@ -3290,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:
|
|
@@ -3302,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
|
|
@@ -3331,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)
|
|
@@ -3368,7 +3955,7 @@ def purge(vm_, dirs=False, removables=None, **kwargs):
|
|
|
|
|
|
def virt_type():
|
|
- '''
|
|
+ """
|
|
Returns the virtual machine type as a string
|
|
|
|
CLI Example:
|
|
@@ -3376,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:
|
|
|
|
@@ -3470,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:
|
|
@@ -3488,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:
|
|
|
|
@@ -3528,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])
|
|
@@ -3544,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_))
|
|
@@ -3558,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:
|
|
|
|
@@ -3597,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_:
|
|
@@ -3639,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:
|
|
|
|
@@ -3675,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_:
|
|
@@ -3718,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
|
|
@@ -3763,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
|
|
@@ -3800,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)
|
|
@@ -3820,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
|
|
@@ -3848,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)
|
|
@@ -3858,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
|
|
@@ -3888,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)
|
|
@@ -3896,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
|
|
@@ -3920,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()
|
|
@@ -3936,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()
|
|
|
|
@@ -3946,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 = ""
|
|
@@ -3961,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
|
|
@@ -4170,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
|
|
@@ -4333,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()
|
|
@@ -4346,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
|
|
@@ -4361,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
|
|
@@ -4409,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
|
|
@@ -4523,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,
|
|
@@ -4540,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
|
|
@@ -4570,7 +5226,7 @@ def network_define(name,
|
|
|
|
|
|
def list_networks(**kwargs):
|
|
- '''
|
|
+ """
|
|
List all virtual networks.
|
|
|
|
:param connection: libvirt connection URI, overriding defaults
|
|
@@ -4584,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()]
|
|
@@ -4593,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
|
|
@@ -4610,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
|
|
@@ -4660,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()
|
|
@@ -4669,7 +5331,7 @@ def network_get_xml(name, **kwargs):
|
|
|
|
|
|
def network_start(name, **kwargs):
|
|
- '''
|
|
+ """
|
|
Start a defined virtual network.
|
|
|
|
:param name: virtual network name
|
|
@@ -4684,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)
|
|
@@ -4694,7 +5356,7 @@ def network_start(name, **kwargs):
|
|
|
|
|
|
def network_stop(name, **kwargs):
|
|
- '''
|
|
+ """
|
|
Stop a defined virtual network.
|
|
|
|
:param name: virtual network name
|
|
@@ -4709,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)
|
|
@@ -4719,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
|
|
@@ -4734,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)
|
|
@@ -4743,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.
|
|
|
|
@@ -4762,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.
|
|
@@ -4819,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
|
|
@@ -5070,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)
|
|
|
|
@@ -5086,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:
|
|
@@ -5095,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()
|
|
@@ -5104,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.
|
|
|
|
@@ -5268,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
|
|
@@ -5278,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)
|
|
@@ -5313,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
|
|
@@ -5349,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()]
|
|
@@ -5358,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
|
|
@@ -5375,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
|
|
@@ -5428,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()
|
|
@@ -5437,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
|
|
@@ -5452,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)
|
|
@@ -5462,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
|
|
@@ -5477,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)
|
|
@@ -5487,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
|
|
@@ -5502,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)
|
|
@@ -5512,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
|
|
@@ -5527,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)
|
|
@@ -5537,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
|
|
@@ -5552,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
|
|
@@ -5577,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)
|
|
@@ -5586,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.
|
|
|
|
@@ -5605,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
|
|
@@ -5630,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)
|
|
@@ -5640,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()
|
|
@@ -5672,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.
|
|
@@ -5703,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:
|
|
@@ -5714,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
|
|
@@ -5775,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.28.0
|
|
|
|
|