From 2463ebe5a82b1a017004e8e0e390535485dc703e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= Date: Wed, 5 Sep 2018 12:21:30 +0200 Subject: [PATCH] virt: allow defining the VM type and arch when creating it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some hypervisors can handle several CPU architectures or have different virtualization types. This is reflected in libvirt by the OS type (badly named, indeed) and the arch value. Allow users to set them when creating a VM using either virt.init or virt.running. Signed-off-by: Cédric Bosdonnat --- salt/modules/virt.py | 40 +++++++++++----- salt/states/virt.py | 17 ++++++- salt/templates/virt/libvirt_domain.jinja | 2 +- tests/unit/modules/test_virt.py | 58 ++++++++++++++++++++++-- tests/unit/states/test_virt.py | 5 ++ 5 files changed, 104 insertions(+), 18 deletions(-) diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 20d2735a5000..5a105c73f80e 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -244,14 +244,6 @@ def __get_conn(**kwargs): return conn -def _get_domain_types(**kwargs): - ''' - Return the list of possible values for the type attribute. - ''' - caps = capabilities(**kwargs) - return sorted(set([x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y])) - - def _get_domain(conn, *vms, **kwargs): ''' Return a domain object for the named VM or return domain object for all VMs. @@ -557,6 +549,8 @@ def _gen_xml(name, diskp, nicp, hypervisor, + os_type, + arch, graphics=None, loader=None, **kwargs): @@ -638,6 +632,9 @@ def _gen_xml(name, context['disks'][disk['name']]['index'] = six.text_type(i) context['nics'] = nicp + context['os_type'] = os_type + context['arch'] = arch + fn_ = 'libvirt_domain.jinja' try: template = JINJA.get_template(fn_) @@ -1217,6 +1214,8 @@ def init(name, enable_qcow=False, graphics=None, loader=None, + os_type=None, + arch=None, **kwargs): ''' Initialize a new vm @@ -1281,6 +1280,16 @@ def init(name, See :ref:`init-loader-def` for more details on the possible values. .. versionadded:: Fluorine + :param os_type: + type of virtualization as found in the ``//os/type`` element of the libvirt definition. + The default value is taken from the host capabilities, with a preference for ``hvm``. + + .. versionadded:: Neon + :param arch: + architecture of the virtual machine. The default value is taken from the host capabilities, + but ``x86_64`` is prefed over ``i686``. + + .. versionadded:: Neon :param enable_qcow: ``True`` to create a QCOW2 overlay image, rather than copying the image (Default: ``False``). @@ -1478,7 +1487,10 @@ def init(name, virt: images: /data/my/vm/images/ ''' - hypervisors = _get_domain_types(**kwargs) + caps = capabilities(**kwargs) + os_types = sorted(set([guest['os_type'] for guest in caps['guests']])) + arches = sorted(set([guest['arch']['name'] for guest in caps['guests']])) + hypervisors = sorted(set([x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y])) hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor) if hypervisor is not None: salt.utils.versions.warn_until( @@ -1610,7 +1622,10 @@ def init(name, '\'enable_vnc\' will be removed in {version}. ') graphics = {'type': 'vnc'} - vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, graphics, loader, **kwargs) + os_type = 'hvm' if 'hvm' in os_types else os_types[0] + arch = 'x86_64' if 'x86_64' in arches else arches[0] + + vm_xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, os_type, arch, graphics, loader, **kwargs) conn = __get_conn(**kwargs) try: conn.defineXML(vm_xml) @@ -1854,6 +1869,8 @@ def update(name, _disk_profile(disk_profile, hypervisor, disks, name, **kwargs), _get_merged_nics(hypervisor, nic_profile, interfaces), hypervisor, + domain.OSType(), + desc.find('.//os/type').get('arch'), graphics, **kwargs)) @@ -2564,7 +2581,8 @@ def get_profiles(hypervisor=None, **kwargs): ''' ret = {} - hypervisors = _get_domain_types(**kwargs) + caps = capabilities(**kwargs) + hypervisors = sorted(set([x for y in [guest['arch']['domains'].keys() for guest in caps['guests']] for x in y])) default_hypervisor = 'kvm' if 'kvm' in hypervisors else hypervisors[0] if not hypervisor: diff --git a/salt/states/virt.py b/salt/states/virt.py index 32ad5910c074..13454ac358b0 100644 --- a/salt/states/virt.py +++ b/salt/states/virt.py @@ -254,7 +254,9 @@ def running(name, update=False, connection=None, username=None, - password=None): + password=None, + os_type=None, + arch=None): ''' Starts an existing guest, or defines and starts a new VM with specified arguments. @@ -332,6 +334,17 @@ def running(name, :param password: password to connect with, overriding defaults .. versionadded:: Fluorine + :param os_type: + type of virtualization as found in the ``//os/type`` element of the libvirt definition. + The default value is taken from the host capabilities, with a preference for ``hvm``. + Only used when creating a new virtual machine. + + .. versionadded:: Neon + :param arch: + architecture of the virtual machine. The default value is taken from the host capabilities, + but ``x86_64`` is prefed over ``i686``. Only used when creating a new virtual machine. + + .. versionadded:: Neon .. rubric:: Example States @@ -435,6 +448,8 @@ def running(name, __salt__['virt.init'](name, cpu=cpu, mem=mem, + os_type=os_type, + arch=arch, image=image, hypervisor=vm_type, disk=disk_profile, diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja index 2db9f2311e2e..109293a55046 100644 --- a/salt/templates/virt/libvirt_domain.jinja +++ b/salt/templates/virt/libvirt_domain.jinja @@ -3,7 +3,7 @@ {{ cpu }} {{ mem }} - hvm + {{ os_type }} {% for dev in boot_dev %} {% endfor %} diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index 4d03206fc730..83f07f00ca12 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -116,10 +116,14 @@ def test_boot_default_dev(self): 512, diskp, nicp, - 'kvm' + 'kvm', + 'hvm', + 'x86_64' ) root = ET.fromstring(xml_data) self.assertEqual(root.find('os/boot').attrib['dev'], 'hd') + self.assertEqual(root.find('os/type').attrib['arch'], 'x86_64') + self.assertEqual(root.find('os/type').text, 'hvm') def test_boot_custom_dev(self): ''' @@ -134,6 +138,8 @@ def test_boot_custom_dev(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', boot_dev='cdrom' ) root = ET.fromstring(xml_data) @@ -152,6 +158,8 @@ def test_boot_multiple_devs(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', boot_dev='cdrom network' ) root = ET.fromstring(xml_data) @@ -171,6 +179,8 @@ def test_gen_xml_for_serial_console(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', serial_type='pty', console=True ) @@ -191,6 +201,8 @@ def test_gen_xml_for_telnet_console(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', serial_type='tcp', console=True, telnet_port=22223 @@ -213,6 +225,8 @@ def test_gen_xml_for_telnet_console_unspecified_port(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', serial_type='tcp', console=True ) @@ -234,6 +248,8 @@ def test_gen_xml_for_serial_no_console(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', serial_type='pty', console=False ) @@ -254,6 +270,8 @@ def test_gen_xml_for_telnet_no_console(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', serial_type='tcp', console=False, ) @@ -273,7 +291,9 @@ def test_gen_xml_nographics_default(self): 512, diskp, nicp, - 'kvm' + 'kvm', + 'hvm', + 'x86_64' ) root = ET.fromstring(xml_data) self.assertIsNone(root.find('devices/graphics')) @@ -290,7 +310,9 @@ def test_gen_xml_noloader_default(self): 512, diskp, nicp, - 'kvm' + 'kvm', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) self.assertIsNone(root.find('os/loader')) @@ -308,6 +330,8 @@ def test_gen_xml_vnc_default(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', graphics={'type': 'vnc', 'port': 1234, 'tlsPort': 5678, 'listen': {'type': 'address', 'address': 'myhost'}}, ) @@ -333,6 +357,8 @@ def test_gen_xml_spice_default(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', graphics={'type': 'spice'}, ) root = ET.fromstring(xml_data) @@ -355,6 +381,8 @@ def test_gen_xml_loader_default(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', loader={'path': '/foo/bar', 'readonly': 'yes', 'type': 'pflash', 'secure': 'yes'} ) root = ET.fromstring(xml_data) @@ -376,6 +404,8 @@ def test_gen_xml_loader_invalid(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', loader={'readonly': 'yes', 'type': 'pflash', 'secure': 'yes'} ) root = ET.fromstring(xml_data) @@ -394,6 +424,8 @@ def test_gen_xml_spice(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', graphics={'type': 'spice', 'port': 1234, 'tls_port': 5678, 'listen': {'type': 'none'}}, ) root = ET.fromstring(xml_data) @@ -487,6 +519,8 @@ def test_gen_xml_for_kvm_default_profile(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) self.assertEqual(root.attrib['type'], 'kvm') @@ -529,6 +563,8 @@ def test_gen_xml_for_esxi_default_profile(self): diskp, nicp, 'vmware', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) self.assertEqual(root.attrib['type'], 'vmware') @@ -583,6 +619,8 @@ def test_gen_xml_for_esxi_custom_profile(self): diskp, nicp, 'vmware', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) self.assertEqual(root.attrib['type'], 'vmware') @@ -619,6 +657,8 @@ def test_gen_xml_for_kvm_custom_profile(self): diskp, nicp, 'kvm', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) self.assertEqual(root.attrib['type'], 'kvm') @@ -689,7 +729,9 @@ def test_controller_for_esxi(self): 512, diskp, nicp, - 'vmware' + 'vmware', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) controllers = root.findall('.//devices/controller') @@ -709,7 +751,9 @@ def test_controller_for_kvm(self): 512, diskp, nicp, - 'kvm' + 'kvm', + 'hvm', + 'x86_64', ) root = ET.fromstring(xml_data) controllers = root.findall('.//devices/controller') @@ -813,6 +857,9 @@ def test_update(self): 1048576 1048576 1 + + hvm + @@ -858,6 +905,7 @@ def test_update(self): ''' domain_mock = self.set_mock_vm('myvm', xml) + domain_mock.OSType = MagicMock(return_value='hvm') define_mock = MagicMock(return_value=True) self.mock_conn.defineXML = define_mock diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py index 193ca08cb705..a85fd0cffb1f 100644 --- a/tests/unit/states/test_virt.py +++ b/tests/unit/states/test_virt.py @@ -253,6 +253,7 @@ def test_running(self): mem=2048, image='/path/to/img.qcow2'), ret) init_mock.assert_called_with('myvm', cpu=2, mem=2048, image='/path/to/img.qcow2', + os_type=None, arch=None, disk=None, disks=None, nic=None, interfaces=None, graphics=None, loader=None, hypervisor=None, seed=True, install=True, pub_key=None, priv_key=None, @@ -290,6 +291,8 @@ def test_running(self): self.assertDictEqual(virt.running('myvm', cpu=2, mem=2048, + os_type='linux', + arch='i686', vm_type='qemu', disk_profile='prod', disks=disks, @@ -307,6 +310,8 @@ def test_running(self): init_mock.assert_called_with('myvm', cpu=2, mem=2048, + os_type='linux', + arch='i686', image=None, disk='prod', disks=disks,