Merge pull request #48421 from cbosdo/virt-running

Virt states improvements
This commit is contained in:
Nicole Thomas 2018-07-09 09:33:49 -04:00 committed by GitHub
commit 2f2a0ee320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1064 additions and 131 deletions

View File

@ -694,16 +694,32 @@ def _gen_net_xml(name,
def _gen_pool_xml(name,
ptype,
target,
source=None):
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):
'''
Generate the XML string to define a libvirt storage pool
'''
hosts = [host.split(':') for host in source_hosts or []]
context = {
'name': name,
'ptype': ptype,
'target': target,
'source': source,
'target': {'path': target, 'permissions': permissions},
'source': {
'devices': source_devices or [],
'dir': source_dir,
'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
}
}
fn_ = 'libvirt_pool.jinja'
try:
@ -3909,7 +3925,7 @@ def cpu_baseline(full=False, migratable=False, out='libvirt', **kwargs):
return cpu.toxml()
def net_define(name, bridge, forward, **kwargs):
def network_define(name, bridge, forward, **kwargs):
'''
Create libvirt network.
@ -3928,7 +3944,7 @@ def net_define(name, bridge, forward, **kwargs):
.. code-block:: bash
salt '*' virt.net_define network main bridge openvswitch
salt '*' virt.network_define network main bridge openvswitch
.. versionadded:: Fluorine
'''
@ -4146,85 +4162,166 @@ def network_set_autostart(name, state='on', **kwargs):
conn.close()
def pool_define_build(name, **kwargs):
def pool_define(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,
transient=False,
start=True, # pylint: disable=redefined-outer-name
**kwargs):
'''
Create libvirt pool.
:param name: Pool name
:param ptype: Pool type
:param target: Pool path target
:param source: Pool dev source
:param autostart: Pool autostart (default True)
:param ptype:
Pool type. See `libvirt documentation <https://libvirt.org/storage.html>`_ for the
possible values.
:param target: Pool full path target
: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 source_devices:
List of source devices for pools backed by physical devices. (Default: ``None``)
Each item in the list is a dictionary with ``path`` and optionally ``part_separator``
keys. The path is the qualified name for iSCSI devices.
Report to `this libvirt page <https://libvirt.org/formatstorage.html#StoragePool>`_
for more informations on the use of ``part_separator``
:param source_dir:
Path to the source directory for pools of type ``dir``, ``netfs`` or ``gluster``.
(Default: ``None``)
:param source_adapter:
SCSI source definition. The value is a dictionary with ``type``, ``name``, ``parent``,
``managed``, ``parent_wwnn``, ``parent_wwpn``, ``parent_fabric_wwn``, ``wwnn``, ``wwpn``
and ``parent_address`` keys.
The ``parent_address`` value is a dictionary with ``unique_id`` and ``address`` keys.
The address represents a PCI address and is itself a dictionary with ``domain``, ``bus``,
``slot`` and ``function`` properties.
Report to `this libvirt page <https://libvirt.org/formatstorage.html#StoragePool>`_
for the meaning and possible values of these properties.
:param source_hosts:
List of source for pools backed by storage from remote servers. Each item is the hostname
optionally followed by the port separated by a colon. (Default: ``None``)
:param source_auth:
Source authentication details. (Default: ``None``)
The value is a dictionary with ``type``, ``username`` and ``secret`` keys. The type
can be one of ``ceph`` for Ceph RBD or ``chap`` for iSCSI sources.
The ``secret`` value links to a libvirt secret object. It is a dictionary with
``type`` and ``value`` keys. The type value can be either ``uuid`` or ``usage``.
Examples:
.. code-block:: python
source_auth={
'type': 'ceph',
'username': 'admin',
'secret': {
'type': 'uuid',
'uuid': '2ec115d7-3a88-3ceb-bc12-0ac909a6fd87'
}
}
.. code-block:: python
source_auth={
'type': 'chap',
'username': 'myname',
'secret': {
'type': 'usage',
'uuid': 'mycluster_myname'
}
}
:param source_name:
Identifier of name-based sources.
:param source_format:
String representing the source format. The possible values are depending on the
source type. See `libvirt documentation <https://libvirt.org/storage.html>`_ for
the possible values.
:param start: Pool start (default True)
:param transient:
When ``True``, the pool will be automatically undefined after being stopped.
Note that a transient pool will force ``start`` to ``True``. (Default: ``False``)
: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:
.. _pool-define-permissions:
.. rubric:: Permissions definition
The permissions are described by a dictionary containing the following keys:
mode
The octal representation of the permissions. (Default: `0711`)
owner
the numeric user ID of the owner. (Default: from the parent folder)
group
the numeric ID of the group. (Default: from the parent folder)
label
the SELinux label. (Default: `None`)
.. rubric:: CLI Example:
Local folder pool:
.. code-block:: bash
salt '*' virt.pool_define base logical base
salt '*' virt.pool_define somepool dir target=/srv/mypool \
permissions="{'mode': '0744' 'ower': 107, 'group': 107 }"
CIFS backed pool:
.. code-block:: bash
salt '*' virt.pool_define myshare netfs source_format=cifs \
source_dir=samba_share source_hosts="['example.com']" target=/mnt/cifs
.. versionadded:: Fluorine
'''
exist = False
update = False
conn = __get_conn(**kwargs)
ptype = kwargs.pop('ptype', None)
target = kwargs.pop('target', None)
source = kwargs.pop('source', None)
autostart = kwargs.pop('autostart', True)
starting = kwargs.pop('start', True)
pool_xml = _gen_pool_xml(
name,
ptype,
target,
source,
permissions=permissions,
source_devices=source_devices,
source_dir=source_dir,
source_adapter=source_adapter,
source_hosts=source_hosts,
source_auth=source_auth,
source_name=source_name,
source_format=source_format
)
try:
conn.storagePoolDefineXML(pool_xml)
except libvirtError as err:
log.warning(err)
if err.get_error_code() == libvirt.VIR_ERR_STORAGE_POOL_BUILT or libvirt.VIR_ERR_OPERATION_FAILED:
exist = True
if transient:
pool = conn.storagePoolCreateXML(pool_xml)
else:
conn.close()
raise err # a real error we should report upwards
try:
pool = conn.storagePoolLookupByName(name)
pool = conn.storagePoolDefineXML(pool_xml)
if start:
pool.create()
except libvirtError as err:
log.warning(err)
conn.close()
raise err # a real error we should report upwards
if pool is None:
finally:
conn.close()
return False
if (starting is True or autostart is True) and pool.isActive() != 1:
if exist is True:
update = True
pool.create()
else:
pool.create(libvirt.VIR_STORAGE_POOL_CREATE_WITH_BUILD)
if autostart is True and pool.autostart() != 1:
if exist is True:
update = True
pool.setAutostart(int(autostart))
elif autostart is False and pool.autostart() == 1:
if exist is True:
update = True
pool.setAutostart(int(autostart))
conn.close()
if exist is True:
if update is True:
return (True, 'Pool exist', 'Pool update')
return (True, 'Pool exist')
# libvirt function will raise a libvirtError in case of failure
return True

View File

@ -27,6 +27,7 @@ except ImportError:
import salt.utils.args
import salt.utils.files
import salt.utils.stringutils
import salt.utils.versions
from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
@ -144,7 +145,8 @@ def keys(name, basepath='/etc/pki', **kwargs):
return ret
def _virt_call(domain, function, section, comment, **kwargs):
def _virt_call(domain, function, section, comment,
connection=None, username=None, password=None, **kwargs):
'''
Helper to call the virt functions. Wildcards supported.
@ -160,7 +162,11 @@ def _virt_call(domain, function, section, comment, **kwargs):
ignored_domains = list()
for targeted_domain in targeted_domains:
try:
response = __salt__['virt.{0}'.format(function)](targeted_domain, **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})
@ -178,54 +184,183 @@ def _virt_call(domain, function, section, comment, **kwargs):
return ret
def stopped(name):
def stopped(name, connection=None, username=None, password=None):
'''
Stops a VM by shutting it down nicely.
.. versionadded:: 2016.3.0
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
.. code-block:: yaml
domain_name:
virt.stopped
'''
return _virt_call(name, 'shutdown', 'stopped', "Machine has been shut down")
return _virt_call(name, 'shutdown', 'stopped', "Machine has been shut down",
connection=connection, username=username, password=password)
def powered_off(name):
def powered_off(name, connection=None, username=None, password=None):
'''
Stops a VM by power off.
.. versionadded:: 2016.3.0
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
.. code-block:: yaml
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',
connection=connection, username=username, password=password)
def running(name, **kwargs):
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,
connection=None,
username=None,
password=None):
'''
Starts an existing guest, or defines and starts a new VM with specified arguments.
.. versionadded:: 2016.3.0
: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:: Fluorine
: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.
.. versionadded:: Fluorine
:param disk_profile:
Name of the disk profile to use for the new virtual machine
.. versionadded:: Fluorine
:param disks:
List of disk to create for the new virtual machine.
See :ref:`init-disk-def` for more details on the items on this list.
.. versionadded:: Fluorine
:param nic_profile:
Name of the network interfaces profile to use for the new virtual machine
.. versionadded:: Fluorine
:param interfaces:
List of network interfaces to create for the new virtual machine.
See :ref:`init-nic-def` for more details on the items on this list.
.. versionadded:: Fluorine
:param graphics:
Graphics device to create for the new virtual machine.
See :ref:`init-graphics-def` for more details on this dictionary
.. versionadded:: Fluorine
:param saltenv:
Fileserver environment (Default: ``'base'``).
See :mod:`cp module for more details <salt.modules.cp>`
.. versionadded:: Fluorine
:param seed: ``True`` to seed the disk image. Only used when the ``image`` parameter is provided.
(Default: ``True``)
.. versionadded:: Fluorine
:param install: install salt minion if absent (Default: ``True``)
.. versionadded:: Fluorine
:param pub_key: public key to seed with (Default: ``None``)
.. versionadded:: Fluorine
:param priv_key: public key to seed with (Default: ``None``)
.. versionadded:: Fluorine
:param seed_cmd: Salt command to execute to seed the image. (Default: ``'seed.apply'``)
.. versionadded:: Fluorine
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
.. rubric:: Example States
Make sure an already-defined virtual machine called ``domain_name`` is running:
.. code-block:: yaml
domain_name:
virt.running
Do the same, but define the virtual machine if needed:
.. code-block:: yaml
domain_name:
virt.running:
- cpu: 2
- mem: 2048
- eth0_mac: 00:00:6a:53:00:e3
- disk_profile: prod
- disks:
- name: system
size: 8192
overlay_image: True
pool: default
image: /path/to/image.qcow2
- name: data
size: 16834
- nic_profile: prod
- interfaces:
- 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.125
'''
@ -235,11 +370,6 @@ def running(name, **kwargs):
'comment': '{0} is running'.format(name)
}
kwargs = salt.utils.args.clean_kwargs(**kwargs)
cpu = kwargs.pop('cpu', False)
mem = kwargs.pop('mem', False)
image = kwargs.pop('image', False)
try:
try:
__salt__['virt.vm_state'](name)
@ -250,8 +380,29 @@ def running(name, **kwargs):
else:
ret['comment'] = 'Domain {0} exists and is running'.format(name)
except CommandExecutionError:
kwargs = salt.utils.args.clean_kwargs(**kwargs)
__salt__['virt.init'](name, cpu=cpu, mem=mem, image=image, **kwargs)
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}.'
)
__salt__['virt.init'](name,
cpu=cpu,
mem=mem,
image=image,
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)
ret['changes'][name] = 'Domain defined and started'
ret['comment'] = 'Domain {0} defined and started'.format(name)
except libvirt.libvirtError as err:
@ -262,12 +413,22 @@ def running(name, **kwargs):
return ret
def snapshot(name, suffix=None):
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
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
.. code-block:: yaml
domain_name:
@ -279,21 +440,32 @@ def snapshot(name, suffix=None):
- suffix: periodic
'''
return _virt_call(name, 'snapshot', 'saved', 'Snapshot has been taken', suffix=suffix)
return _virt_call(name, 'snapshot', 'saved', 'Snapshot has been taken', suffix=suffix,
connection=connection, username=username, password=password)
# Deprecated states
def rebooted(name):
def rebooted(name, connection=None, username=None, password=None):
'''
Reboots VMs
.. versionadded:: 2016.3.0
:param name:
:return:
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
'''
return _virt_call(name, 'reboot', 'rebooted', "Machine has been rebooted")
return _virt_call(name, 'reboot', 'rebooted', "Machine has been rebooted",
connection=connection, username=username, password=password)
def unpowered(name):
@ -396,10 +568,28 @@ def reverted(name, snapshot=None, cleanup=False): # pylint: disable=redefined-o
return ret
def network_define(name, bridge, forward, **kwargs):
def network_running(name,
bridge,
forward,
vport=None,
tag=None,
autostart=True,
connection=None,
username=None,
password=None):
'''
Defines and starts a new network with specified arguments.
:param connection: libvirt connection URI, overriding defaults
.. versionadded:: Fluorine
:param username: username to connect with, overriding defaults
.. versionadded:: Fluorine
:param password: password to connect with, overriding defaults
.. versionadded:: Fluorine
.. code-block:: yaml
domain_name:
@ -414,42 +604,75 @@ def network_define(name, bridge, forward, **kwargs):
- vport: openvswitch
- tag: 180
- autostart: True
- start: True
'''
ret = {'name': name,
'changes': {},
'result': False,
'result': True,
'comment': ''
}
kwargs = salt.utils.args.clean_kwargs(**kwargs)
vport = kwargs.pop('vport', None)
tag = kwargs.pop('tag', None)
autostart = kwargs.pop('autostart', True)
start = kwargs.pop('start', True)
try:
result = __salt__['virt.net_define'](name, bridge, forward, vport, tag=tag, autostart=autostart, start=start)
if result:
ret['changes'][name] = 'Network {0} has been created'.format(name)
ret['result'] = True
info = __salt__['virt.network_info'](name, connection=connection, username=username, password=password)
if info:
if info['active']:
ret['comment'] = 'Network {0} exists and is running'.format(name)
else:
__salt__['virt.network_start'](name, connection=connection, username=username, password=password)
ret['changes'][name] = 'Network started'
ret['comment'] = 'Network {0} started'.format(name)
else:
ret['comment'] = 'Network {0} created fail'.format(name)
__salt__['virt.network_define'](name,
bridge,
forward,
vport,
tag=tag,
autostart=autostart,
start=True,
connection=connection,
username=username,
password=password)
ret['changes'][name] = 'Network defined and started'
ret['comment'] = 'Network {0} defined and started'.format(name)
except libvirt.libvirtError as err:
if err.get_error_code() == libvirt.VIR_ERR_NETWORK_EXIST or libvirt.VIR_ERR_OPERATION_FAILED:
ret['result'] = True
ret['comment'] = 'The network already exist'
else:
ret['comment'] = err.get_error_message()
ret['result'] = False
ret['comment'] = err.get_error_message()
return ret
def pool_define(name, **kwargs):
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:: Fluorine
:param ptype: libvirt pool type
:param target: full path to the target device or folder. (Default: ``None``)
:param permissions:
target permissions. See :ref:`pool-define-permissions` for more details on this structure.
:param source:
dictionary containing keys matching the ``source_*`` parameters in function
:func:`salt.modules.virt.pool_define`.
:param transient:
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
.. code-block:: yaml
pool_name:
@ -459,44 +682,73 @@ def pool_define(name, **kwargs):
pool_name:
virt.pool_define:
- ptype: logical
- target: pool
- source: sda1
- ptype: netfs
- target: /mnt/cifs
- permissions:
- mode: 0770
- owner: 1000
- group: 100
- source:
- dir: samba_share
- hosts:
one.example.com
two.example.com
- format: cifs
- autostart: True
- start: True
'''
ret = {'name': name,
'changes': {},
'result': False,
'result': True,
'comment': ''
}
kwargs = salt.utils.args.clean_kwargs(**kwargs)
ptype = kwargs.pop('ptype', None)
target = kwargs.pop('target', None)
source = kwargs.pop('source', None)
autostart = kwargs.pop('autostart', True)
start = kwargs.pop('start', True)
try:
result = __salt__['virt.pool_define_build'](name, ptype=ptype, target=target,
source=source, autostart=autostart, start=start)
if result:
if 'Pool exist' in result:
if 'Pool update' in result:
ret['changes'][name] = 'Pool {0} has been updated'.format(name)
else:
ret['comment'] = 'Pool {0} already exist'.format(name)
info = __salt__['virt.pool_info'](name, connection=connection, username=username, password=password)
if info:
if info['state'] == 'running':
ret['comment'] = 'Pool {0} exists and is running'.format(name)
else:
ret['changes'][name] = 'Pool {0} has been created'.format(name)
ret['result'] = True
__salt__['virt.pool_start'](name, connection=connection, username=username, password=password)
ret['changes'][name] = 'Pool started'
ret['comment'] = 'Pool {0} started'.format(name)
else:
ret['comment'] = 'Pool {0} created fail'.format(name)
__salt__['virt.pool_define'](name,
ptype=ptype,
target=target,
permissions=permissions,
source_devices=(source or {}).get('devices', None),
source_dir=(source or {}).get('dir', None),
source_adapter=(source or {}).get('adapter', None),
source_hosts=(source or {}).get('hosts', None),
source_auth=(source or {}).get('auth', None),
source_name=(source or {}).get('name', None),
source_format=(source or {}).get('format', None),
transient=transient,
start=True,
connection=connection,
username=username,
password=password)
if autostart:
__salt__['virt.pool_set_autostart'](name,
state='on' if autostart else 'off',
connection=connection,
username=username,
password=password)
__salt__['virt.pool_build'](name,
connection=connection,
username=username,
password=password)
__salt__['virt.pool_start'](name,
connection=connection,
username=username,
password=password)
ret['changes'][name] = 'Pool defined and started'
ret['comment'] = 'Pool {0} defined and started'.format(name)
except libvirt.libvirtError as err:
if err.get_error_code() == libvirt.VIR_ERR_STORAGE_POOL_BUILT or libvirt.VIR_ERR_OPERATION_FAILED:
ret['result'] = True
ret['comment'] = 'The pool already exist'
ret['comment'] = err.get_error_message()
ret['result'] = False
return ret

View File

@ -1,9 +1,64 @@
<pool type='{{ ptype }}'>
<name>{{ name }}</name>
{% if target.path or target.permissions %}
<target>
<path>/dev/{{ target }}</path>
</target>{% if source != None %}
{% if target.path %}
<path>{{ target.path }}</path>
{% endif %}
{% if target.permissions %}
<permissions>
{% for key in ['mode', 'owner', 'group', 'label'] %}
{% if key in target.permissions %}
<{{ key }}>{{ target.permissions[key] }}</{{ key }}>
{% endif %}
{% endfor %}
</permissions>
{% endif %}
</target>
{% endif %}
{% if source %}
<source>
<device path='/dev/{{ source }}'/>
</source>{% endif %}
</pool>
{% if ptype in ['fs', 'logical', 'disk', 'iscsi', 'zfs', 'vstorage'] %}
{% for device in source.devices %}
<device path='{{ device.path }}'
{% if 'part_separator' in device %}part_separator='{{ device.part_separator }}'{% endif %}/>
{% endfor %}
{% elif ptype in ['dir', 'netfs', 'gluster'] %}
<dir path='{{ source.dir }}'/>
{% elif ptype == 'scsi' %}
<adapter type='{{ source.adapter.type }}'
{% for key in ['name', 'parent', 'managed', 'parent_wwnn', 'parent_wwpn', 'parent_fabric_wwn', 'wwnn', 'wwpn'] %}
{% if key in source.adapter %}{{ key }}='{{ source.adapter[key] }}'{% endif %}
{% endfor %}>
{% if 'parent_address' in source.adapter %}
<parentaddr
{% if 'unique_id' in source.adapter.parent_address %}
unique_id='{{ source.adapter.parent_address.unique_id }}'
{% endif %}>
<address
{% for key in source.adapter.parent_address.address %}
{{ key }}='{{ source.adapter.parent_address.address[key] }}'
{% endfor %}/>
</parentaddr>
{% endif %}
</adapter>
{% endif %}
{% if ptype in ['netfs', 'iscsi', 'rbd', 'sheepdog', 'gluster'] and source.hosts %}
{% for host in source.hosts %}
<host name='{{ host.name }}' {% if host.port %}port='{{ host.port }}'{% endif %}/>
{% endfor %}
{% endif %}
{% if ptype in ['iscsi', 'rbd'] and source.auth %}
<auth type='{{source.auth.type}}' username='{{ source.auth.username }}'>
<secret type='{{ source.auth.secret.type }}' {{ source.auth.secret.type }}='{{source.auth.secret.value}}'/>
</auth>
{% endif %}
{% if ptype in ['logical', 'rbd', 'sheepdog', 'gluster'] and source.name %}
<name>{{ source.name }}</name>
{% endif %}
{% if ptype in ['fs', 'netfs', 'logical', 'disk'] and source.format %}
<format type='{{ source.format }}'/>
{% endif %}
</source>
{% endif %}
</pool>

View File

@ -1581,7 +1581,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
'''
Test virt._gen_pool_xml()
'''
xml_data = virt._gen_pool_xml('pool', 'logical', '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')
@ -1591,13 +1591,120 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
'''
Test virt._gen_pool_xml() with a source device
'''
xml_data = virt._gen_pool_xml('pool', 'logical', 'base', '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')
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')
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')
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')
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['type'], 'uuid')
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')
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)
def test_list_pools(self):
'''
Test virt.list_pools()

View File

@ -23,6 +23,10 @@ from tests.support.mock import (
# Import Salt Libs
import salt.states.virt as virt
import salt.utils.files
from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
from salt.ext import six
class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors
@ -35,6 +39,12 @@ class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors
libvirt error mockup
'''
def get_error_message(self):
'''
Fake function return error message
'''
return six.text_type(self)
@skipIf(NO_MOCK, NO_MOCK_REASON)
class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
@ -230,9 +240,421 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
'comment': 'Domain myvm started'})
self.assertDictEqual(virt.running('myvm'), ret)
init_mock = MagicMock(return_value=True)
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.vm_state': MagicMock(side_effect=CommandExecutionError('not found')),
'virt.init': init_mock,
'virt.start': MagicMock(return_value=0)
}):
ret.update({'changes': {'myvm': 'Domain defined and started'},
'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, image='/path/to/img.qcow2',
disk=None, disks=None, nic=None, interfaces=None,
graphics=None, hypervisor=None,
seed=True, install=True, pub_key=None, priv_key=None,
connection=None, username=None, password=None)
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.vm_state': MagicMock(side_effect=CommandExecutionError('not found')),
'virt.init': init_mock,
'virt.start': MagicMock(return_value=0)
}):
ret.update({'changes': {'myvm': 'Domain defined and started'},
'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,
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,
image=None,
disk='prod',
disks=disks,
nic='prod',
interfaces=ifaces,
graphics=graphics,
hypervisor='qemu',
seed=False,
install=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.vm_state': MagicMock(return_value='stopped'),
'virt.start': MagicMock(side_effect=[self.mock_libvirt.libvirtError('libvirt error msg')])
}):
ret.update({'changes': {}, 'result': False, 'comment': 'libvirt error msg'})
self.assertDictEqual(virt.running('myvm'), ret)
def test_stopped(self):
'''
stopped state test cases.
'''
ret = {'name': 'myvm',
'changes': {},
'result': True}
shutdown_mock = MagicMock(return_value=True)
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
'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.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.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__, {'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)
def test_powered_off(self):
'''
powered_off state test cases.
'''
ret = {'name': 'myvm',
'changes': {},
'result': True}
stop_mock = MagicMock(return_value=True)
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.list_domains': MagicMock(return_value=['myvm', 'vm1']),
'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.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.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__, {'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)
def test_snapshot(self):
'''
snapshot state test cases.
'''
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)
def test_rebooted(self):
'''
rebooted state test cases.
'''
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)
def test_network_running(self):
'''
network_running state test cases.
'''
ret = {'name': 'mynet', 'changes': {}, 'result': True, 'comment': ''}
define_mock = MagicMock(return_value=True)
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 and started'},
'comment': 'Network mynet defined and started'})
self.assertDictEqual(virt.network_running('mynet',
'br2',
'bridge',
vport='openvswitch',
tag=180,
autostart=False,
connection='myconnection',
username='user',
password='secret'), ret)
define_mock.assert_called_with('mynet',
'br2',
'bridge',
'openvswitch',
tag=180,
autostart=False,
start=True,
connection='myconnection',
username='user',
password='secret')
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.network_info': MagicMock(return_value={'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)
start_mock = MagicMock(return_value=True)
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.network_info': MagicMock(return_value={'active': False}),
'virt.network_start': start_mock,
'virt.network_define': define_mock,
}):
ret.update({'changes': {'mynet': 'Network started'}, 'comment': 'Network mynet 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={}),
'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)
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']}
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.pool_info': MagicMock(return_value={}),
'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 and started'},
'comment': 'Pool mypool defined and 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,
transient=True,
start=True,
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')
with patch.dict(virt.__salt__, { # pylint: disable=no-member
'virt.pool_info': MagicMock(return_value={'state': 'running'}),
}):
ret.update({'changes': {}, 'comment': 'Pool mypool exists and is 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={'state': 'stopped'}),
'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)