From 8deed909147041f8befad8fee9d27bb81595ed23 Mon Sep 17 00:00:00 2001 From: Cedric Bosdonnat 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(" -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 = "" - rbd_pool_desc = """ - - test-rbd - - - - libvirt-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 = """ - - my_vm - 1048576 - 1048576 - 1 - - hvm - - - - - - - - - - - - -
- - - - """ - 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 = ( - "" - ) - - 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 = """ - - my_vm - 1048576 - 1048576 - 1 - - hvm - - - - - - - - - - - -
- - - - - - -
- - - - - - - - -
- - - - - - - - -
- - - - - - - - - - - - - """ - 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 = """ - - vm - 1048576 - 1048576 - 1 - - hvm - /usr/lib/xen/boot/hvmloader - - - """ - 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 = """ - - vm_with_boot_param - 1048576 - 1048576 - 1 - - hvm - /boot/oldkernel - /boot/initrdold.img - console=ttyS0 ks=http://example.com/old/os/ - /usr/share/old/OVMF_CODE.fd - /usr/share/old/OVMF_VARS.ms.fd - - - - - - - - -
- - - - - - - -
- - - - - - - -
- - - - - - - -
- - - - -