Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP4
salt.20998
opensuse-3000-virt-defined-states-222.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File opensuse-3000-virt-defined-states-222.patch of Package salt.20998
From 8deed909147041f8befad8fee9d27bb81595ed23 Mon Sep 17 00:00:00 2001 From: Cedric Bosdonnat <cbosdonnat@suse.com> Date: Fri, 13 Mar 2020 16:38:08 +0100 Subject: [PATCH] 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. --- salt/modules/virt.py | 44 +- salt/states/virt.py | 268 +++++++--- tests/unit/modules/test_virt.py | 845 +----------------------------- tests/unit/states/test_virt.py | 893 +++++++++++++++++++++++++++----- 4 files changed, 971 insertions(+), 1079 deletions(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 362c2a68b5..7314bf1d6e 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -2579,7 +2579,6 @@ def update( live=True, boot=None, test=False, - boot_dev=None, **kwargs ): """ @@ -2653,17 +2652,9 @@ def update( .. versionadded:: 3000 - :param boot_dev: - Space separated list of devices to boot from sorted by decreasing priority. - Values can be ``hd``, ``fd``, ``cdrom`` or ``network``. - - By default, the value will ``"hd"``. - - .. versionadded:: 3002 - :param test: run in dry-run mode if set to True - .. versionadded:: 3001 + .. versionadded:: sodium :return: @@ -2713,7 +2704,6 @@ def update( new_desc = ElementTree.fromstring( _gen_xml( - conn, name, cpu or 0, mem or 0, @@ -2879,26 +2869,22 @@ def update( # Set the new definition if need_update: # Create missing disks if needed - 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 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 not test: - xml_desc = ElementTree.tostring(desc) - log.debug("Update virtual machine definition: %s", xml_desc) - conn.defineXML(salt.utils.stringutils.to_str(xml_desc)) + conn.defineXML( + salt.utils.stringutils.to_str(ElementTree.tostring(desc)) + ) status["definition"] = True except libvirt.libvirtError as err: conn.close() diff --git a/salt/states/virt.py b/salt/states/virt.py index 200c79d35c..2394d0745e 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -12,6 +12,7 @@ for the generation and signing of certificates for systems running libvirt: """ +import copy import fnmatch import logging import os @@ -285,37 +286,15 @@ def defined( arch=None, boot=None, update=True, - boot_dev=None, ): """ Starts an existing guest, or defines and starts a new VM with specified arguments. - .. versionadded:: 3001 + .. versionadded:: sodium :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 to allocate to the virtual machine in MiB. Since 3002, a dictionary can be used to - contain detailed configuration which support memory allocation or tuning. Supported parameters are ``boot``, - ``current``, ``max``, ``slots``, ``hard_limit``, ``soft_limit``, ``swap_hard_limit`` and ``min_guarantee``. The - structure of the dictionary is documented in :ref:`init-mem-def`. Both decimal and binary base are supported. - Detail unit specification is documented in :ref:`virt-units`. Please note that the value for ``slots`` must be - an integer. - - .. code-block:: python - - { - 'boot': 1g, - 'current': 1g, - 'max': 1g, - 'slots': 10, - 'hard_limit': '1024' - 'soft_limit': '512m' - 'swap_hard_limit': '1g' - 'min_guarantee': '512mib' - } - - .. versionchanged:: 3002 - + :param mem: amount of memory in MiB for the new virtual machine :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. @@ -353,27 +332,23 @@ def defined( but ``x86_64`` is prefed over ``i686``. Only used when creating a new virtual machine. :param boot: - 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. - - Refer to :ref:`init-boot-def` for the complete boot parameters description. + 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. - To update any boot parameters, specify the new path for each. To remove any boot parameters, - pass a None object, for instance: 'kernel': ``None``. + .. code-block:: python - .. versionadded:: 3000 + { + 'kernel': '/root/f8-i386-vmlinuz', + 'initrd': '/root/f8-i386-initrd', + 'cmdline': 'console=ttyS0 ks=http://example.com/f8-i386/os/' + } :param update: set to ``False`` to prevent updating a defined domain. (Default: ``True``) - .. deprecated:: 3001 - - :param boot_dev: - Space separated list of devices to boot from sorted by decreasing priority. - Values can be ``hd``, ``fd``, ``cdrom`` or ``network``. - - By default, the value will ``"hd"``. - - .. versionadded:: 3002 + .. deprecated:: sodium .. rubric:: Example States @@ -385,7 +360,6 @@ def defined( virt.defined: - cpu: 2 - mem: 2048 - - boot_dev: network hd - disk_profile: prod - disks: - name: system @@ -438,7 +412,6 @@ def defined( password=password, boot=boot, test=__opts__["test"], - boot_dev=boot_dev, ) ret["changes"][name] = status if not status.get("definition"): @@ -473,7 +446,6 @@ def defined( password=password, boot=boot, start=False, - boot_dev=boot_dev, ) ret["changes"][name] = {"definition": True} ret["comment"] = "Domain {} defined".format(name) @@ -489,6 +461,7 @@ def running( name, cpu=None, mem=None, + image=None, vm_type=None, disk_profile=None, disks=None, @@ -506,7 +479,6 @@ def running( os_type=None, arch=None, boot=None, - boot_dev=None, ): """ Starts an existing guest, or defines and starts a new VM with specified arguments. @@ -584,7 +556,7 @@ def running( :param update: set to ``True`` to update a defined domain. (Default: ``False``) .. versionadded:: 2019.2.0 - .. deprecated:: 3001 + .. deprecated:: sodium :param connection: libvirt connection URI, overriding defaults .. versionadded:: 2019.2.0 @@ -676,10 +648,32 @@ def running( """ 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( - "Aluminium", + "Magnesium", "'update' parameter has been deprecated. Future behavior will be the one of update=True" "It will be removed in {version}.", ) @@ -701,7 +695,6 @@ def running( arch=arch, boot=boot, update=update, - boot_dev=boot_dev, connection=connection, username=username, password=password, @@ -953,7 +946,7 @@ def network_defined( :param username: username to connect with, overriding defaults :param password: password to connect with, overriding defaults - .. versionadded:: 3001 + .. versionadded:: sodium .. code-block:: yaml @@ -1170,7 +1163,7 @@ def pool_defined( """ Defines a new pool with specified arguments. - .. versionadded:: 3001 + .. versionadded:: sodium :param ptype: libvirt pool type :param target: full path to the target device or folder. (Default: ``None``) @@ -1269,24 +1262,14 @@ def pool_defined( action = "" if info[name]["state"] != "running": - if ptype in BUILDABLE_POOL_TYPES: - if not __opts__["test"]: - # Storage pools build like disk or logical will fail if the disk or LV group - # was already existing. Since we can't easily figure that out, just log the - # possible libvirt error. - try: - __salt__["virt.pool_build"]( - name, - connection=connection, - username=username, - password=password, - ) - except libvirt.libvirtError as err: - log.warning( - "Failed to build libvirt storage pool: %s", - err.get_error_message(), - ) - action = ", built" + if not __opts__["test"]: + __salt__["virt.pool_build"]( + name, + connection=connection, + username=username, + password=password, + ) + action = ", built" action = ( "{}, autostart flag changed".format(action) @@ -1322,22 +1305,9 @@ def pool_defined( password=password, ) - if ptype in BUILDABLE_POOL_TYPES: - # Storage pools build like disk or logical will fail if the disk or LV group - # was already existing. Since we can't easily figure that out, just log the - # possible libvirt error. - try: - __salt__["virt.pool_build"]( - name, - connection=connection, - username=username, - password=password, - ) - except libvirt.libvirtError as err: - log.warning( - "Failed to build libvirt storage pool: %s", - err.get_error_message(), - ) + __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 {} defined, marked for autostart".format(name) @@ -1494,6 +1464,138 @@ def pool_running( return ret +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 + + :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: + virt.pool_running + + .. code-block:: yaml + + pool_name: + virt.pool_running: + - 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 + + """ + 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 + # we may get not get our pool in the info dict and that is normal. + 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, + ) + else: + 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, + ) + + comment = "Pool {}".format(name) + change = "Pool" + if name in ret["changes"]: + comment = "{},".format(ret["comment"]) + change = "{},".format(ret["changes"][name]) + + if action != "already running": + ret["changes"][name] = "{} {}".format(change, action) + + ret["comment"] = "{} {}".format(comment, action) + ret["result"] = result + + except libvirt.libvirtError as err: + ret["comment"] = err.get_error_message() + ret["result"] = False + + return ret + + def pool_deleted(name, purge=False, connection=None, username=None, password=None): """ Deletes a virtual storage pool. diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index e9e73d7b5d..db6ba007b7 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -1849,40 +1849,21 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): self.assertEqual( { "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, + "disk": {"attached": [], "detached": []}, "interface": {"attached": [], "detached": []}, }, - virt.update("my_vm"), + virt.update("my vm"), ) - # mem + cpu case - define_mock.reset_mock() - domain_mock.setMemoryFlags.return_value = 0 - domain_mock.setVcpusFlags.return_value = 0 - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - "mem": True, - "cpu": True, - }, - virt.update("my_vm", mem=2048, cpu=2), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual("2", setxml.find("vcpu").text) - self.assertEqual("2147483648", setxml.find("memory").text) - self.assertEqual(2048 * 1024, domain_mock.setMemoryFlags.call_args[0][0]) - # Same parameters passed than in default virt.defined state case self.assertEqual( { "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, + "disk": {"attached": [], "detached": []}, "interface": {"attached": [], "detached": []}, }, virt.update( - "my_vm", + "my vm", cpu=None, mem=None, disk_profile=None, @@ -1905,829 +1886,15 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): { "definition": True, "cpu": True, - "disk": {"attached": [], "detached": [], "updated": []}, + "disk": {"attached": [], "detached": []}, "interface": {"attached": [], "detached": []}, }, - virt.update("my_vm", cpu=2), + virt.update("my vm", cpu=2), ) setxml = ET.fromstring(define_mock.call_args[0][0]) 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/", - } - - # Update boot devices case - define_mock.reset_mock() - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot_dev="cdrom network hd"), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - ["cdrom", "network", "hd"], - [node.get("dev") for node in setxml.findall("os/boot")], - ) - - # Update unchanged boot devices case - define_mock.reset_mock() - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot_dev="hd"), - ) - define_mock.assert_not_called() - - # Update with boot parameter case - define_mock.reset_mock() - 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/", - ) - - boot_uefi = { - "loader": "/usr/share/OVMF/OVMF_CODE.fd", - "nvram": "/usr/share/OVMF/OVMF_VARS.ms.fd", - } - - 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", - ) - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot={"efi": True}), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("os").attrib.get("firmware"), "efi") - - invalid_boot = { - "loader": "/usr/share/OVMF/OVMF_CODE.fd", - "initrd": "/root/f8-i386-initrd", - } - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", boot=invalid_boot) - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", boot={"efi": "Not a boolean value"}) - - # Update memtune parameter case - memtune = { - "soft_limit": "0.5g", - "hard_limit": "1024", - "swap_hard_limit": "2048m", - "min_guarantee": "1 g", - } - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=memtune), - ) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("memtune").find("soft_limit").text, str(int(0.5 * 1024 ** 3)) - ) - self.assertEqual(setxml.find("memtune").find("soft_limit").get("unit"), "bytes") - self.assertEqual( - setxml.find("memtune").find("hard_limit").text, str(1024 * 1024 ** 2) - ) - self.assertEqual( - setxml.find("memtune").find("swap_hard_limit").text, str(2048 * 1024 ** 2) - ) - self.assertEqual( - setxml.find("memtune").find("min_guarantee").text, str(1 * 1024 ** 3) - ) - - invalid_unit = {"soft_limit": "2HB"} - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", mem=invalid_unit) - - invalid_number = { - "soft_limit": "3.4.MB", - } - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", mem=invalid_number) - - # Update memory case - setmem_mock = MagicMock(return_value=0) - domain_mock.setMemoryFlags = setmem_mock - - 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, str(2048 * 1024 ** 2)) - self.assertEqual(setxml.find("memory").get("unit"), "bytes") - self.assertEqual(setmem_mock.call_args[0][0], 2048 * 1024) - - mem_dict = {"boot": "0.5g", "current": "2g", "max": "1g", "slots": 12} - self.assertEqual( - { - "definition": True, - "mem": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=mem_dict), - ) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("memory").get("unit"), "bytes") - self.assertEqual(setxml.find("memory").text, str(int(0.5 * 1024 ** 3))) - self.assertEqual(setxml.find("maxMemory").text, str(1 * 1024 ** 3)) - self.assertEqual(setxml.find("currentMemory").text, str(2 * 1024 ** 3)) - - max_slot_reverse = { - "slots": "10", - "max": "3096m", - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=max_slot_reverse), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024 ** 2)) - self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10") - - # Update disks case - devattach_mock = MagicMock(return_value=0) - devdetach_mock = MagicMock(return_value=0) - domain_mock.attachDevice = devattach_mock - 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}, - ], - ) - 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 "{}" 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"] - ], - ) - - self.assertListEqual( - ["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) - self.assertEqual(devdetach_mock.call_count, 2) - - # Update nics case - 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"] - ], - ) - 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"])) - 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(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": [], "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")) - - # Update with no diff case - 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" - ) - setmem_mock.reset_mock() - with self.assertRaises(self.mock_libvirt.libvirtError): - 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": [], "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": [], "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_xen_boot_params(self): - """ - Test virt.update() a Xen definition no boot parameter. - """ - root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") - xml_boot = """ - <domain type='xen' id='8'> - <name>vm</name> - <memory unit='KiB'>1048576</memory> - <currentMemory unit='KiB'>1048576</currentMemory> - <vcpu placement='auto'>1</vcpu> - <os> - <type arch='x86_64' machine='xenfv'>hvm</type> - <loader type='rom'>/usr/lib/xen/boot/hvmloader</loader> - </os> - </domain> - """ - domain_mock_boot = self.set_mock_vm("vm", xml_boot) - domain_mock_boot.OSType = MagicMock(return_value="hvm") - define_mock_boot = MagicMock(return_value=True) - define_mock_boot.setVcpusFlags = MagicMock(return_value=0) - self.mock_conn.defineXML = define_mock_boot - self.assertEqual( - { - "cpu": False, - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("vm", cpu=2), - ) - setxml = ET.fromstring(define_mock_boot.call_args[0][0]) - self.assertEqual(setxml.find("os").find("loader").attrib.get("type"), "rom") - self.assertEqual( - setxml.find("os").find("loader").text, "/usr/lib/xen/boot/hvmloader" - ) - - def test_update_existing_boot_params(self): - """ - Test virt.update() with existing boot parameters. - """ - 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> - """ - 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" diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index f03159334b..0a47562074 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -1,21 +1,15 @@ """ :codeauthor: Jayesh Kariya <jayeshk@saltstack.com> """ -# Import Python libs import shutil import tempfile -# Import Salt Libs import salt.states.virt as virt import salt.utils.files from salt.exceptions import CommandExecutionError, SaltInvocationError - -# Import 3rd-party libs 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 @@ -338,7 +332,375 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): "myvm", cpu=2, mem=2048, - boot_dev="cdrom hd", + boot_dev="cdrom hd", + 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, + boot_dev="cdrom hd", + 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) + + # 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/", + } + + 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) + + # 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, boot_dev="cdrom hd"), ret + ) + update_mock.assert_called_with( + "myvm", + cpu=2, + boot_dev="cdrom hd", + 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) + + # Test dry-run mode + 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, + ) + 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, + boot_dev=None, + ) + + # 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, + boot_dev=None, + ) + + 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}): + # 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) + + # 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", @@ -361,7 +723,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): "myvm", cpu=2, mem=2048, - boot_dev="cdrom hd", os_type="linux", arch="i686", disk="prod", @@ -470,13 +831,10 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): "comment": "Domain myvm updated with live update(s) failures", } ) - self.assertDictEqual( - virt.defined("myvm", cpu=2, boot_dev="cdrom hd"), ret - ) + self.assertDictEqual(virt.defined("myvm", cpu=2), ret) update_mock.assert_called_with( "myvm", cpu=2, - boot_dev="cdrom hd", mem=None, disk_profile=None, disks=None, @@ -600,7 +958,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password=None, boot=None, test=True, - boot_dev=None, ) # No changes case @@ -635,7 +992,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password=None, boot=None, test=True, - boot_dev=None, ) def test_running(self): @@ -685,12 +1041,67 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): "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=[{"name": "system", "image": "/path/to/img.qcow2"}], + "myvm", cpu=2, mem=2048, disks=disks, image="/path/to/img.qcow2" ), ret, ) @@ -702,7 +1113,75 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): arch=None, boot=None, disk=None, - disks=[{"name": "system", "image": "/path/to/img.qcow2"}], + 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, @@ -712,7 +1191,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): install=True, pub_key=None, priv_key=None, - boot_dev=None, connection=None, username=None, password=None, @@ -774,7 +1252,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): install=False, pub_key="/path/to/key.pub", priv_key="/path/to/key", - boot_dev="network hd", connection="someconnection", username="libvirtuser", password="supersecret", @@ -799,7 +1276,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): start=False, pub_key="/path/to/key.pub", priv_key="/path/to/key", - boot_dev="network hd", connection="someconnection", username="libvirtuser", password="supersecret", @@ -944,7 +1420,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password=None, boot=None, test=False, - boot_dev=None, ) # Failed definition update case @@ -1063,7 +1538,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password=None, boot=None, test=True, - boot_dev=None, ) start_mock.assert_not_called() @@ -1100,7 +1574,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password=None, boot=None, test=True, - boot_dev=None, ) def test_stopped(self): @@ -1401,38 +1874,231 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): connection="myconnection", username="user", password="secret", - ), - ret, - ) - reboot_mock.assert_called_with( - "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}): + 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.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) + # 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__, {"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) + # 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 + ) + + # Test cases with __opt__['test'] set to True + 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, + ) + 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 + ) + + # 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 + ) def test_network_defined(self): """ @@ -1988,72 +2654,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): password="secret", ) - # Define a pool that doesn't handle build - for mock in mocks: - mocks[mock].reset_mock() - 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="rbd", - source={ - "name": "libvirt-pool", - "hosts": ["ses2.tf.local", "ses3.tf.local"], - "auth": { - "username": "libvirt", - "password": "AQAz+PRdtquBBRAASMv7nlMZYfxIyLw3St65Xw==", - }, - }, - autostart=True, - ), - ret, - ) - mocks["define"].assert_called_with( - "mypool", - ptype="rbd", - target=None, - permissions=None, - source_devices=None, - source_dir=None, - source_adapter=None, - source_hosts=["ses2.tf.local", "ses3.tf.local"], - source_auth={ - "username": "libvirt", - "password": "AQAz+PRdtquBBRAASMv7nlMZYfxIyLw3St65Xw==", - }, - source_name="libvirt-pool", - source_format=None, - source_initiator=None, - start=False, - transient=False, - connection=None, - username=None, - password=None, - ) - mocks["autostart"].assert_called_with( - "mypool", state="on", connection=None, username=None, password=None, - ) - mocks["build"].assert_not_called() - mocks["update"] = MagicMock(return_value=False) for mock in mocks: mocks[mock].reset_mock() @@ -2103,9 +2703,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): for mock in mocks: mocks[mock].reset_mock() mocks["update"] = MagicMock(return_value=True) - mocks["build"] = MagicMock( - side_effect=self.mock_libvirt.libvirtError("Existing VG") - ) with patch.dict( virt.__salt__, { # pylint: disable=no-member @@ -2209,7 +2806,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): ), ret, ) - mocks["build"].assert_not_called() mocks["update"].assert_called_with( "mypool", ptype="logical", @@ -2557,8 +3153,8 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): ): ret.update( { - "changes": {"mypool": "Pool updated, restarted"}, - "comment": "Pool mypool updated, restarted", + "changes": {"mypool": "Pool updated, built, restarted"}, + "comment": "Pool mypool updated, built, restarted", "result": True, } ) @@ -2584,7 +3180,9 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): mocks["start"].assert_called_with( "mypool", connection=None, username=None, password=None ) - mocks["build"].assert_not_called() + mocks["build"].assert_called_with( + "mypool", connection=None, username=None, password=None + ) mocks["update"].assert_called_with( "mypool", ptype="logical", @@ -2705,6 +3303,45 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin): 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, + ) + def test_pool_deleted(self): """ Test the pool_deleted state -- 2.29.2
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor