471a3ec29d
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=150
375 lines
15 KiB
Diff
375 lines
15 KiB
Diff
From 4ce0bc544174fdb00482db4653fb4b0ef411e78b Mon Sep 17 00:00:00 2001
|
|
From: Cedric Bosdonnat <cbosdonnat@suse.com>
|
|
Date: Tue, 3 Sep 2019 15:18:04 +0200
|
|
Subject: [PATCH] Fix virt.full_info (#176)
|
|
|
|
* virt.get_xml doesn't take a domain object
|
|
|
|
In some places in the virt module, the get_xml function was called with
|
|
a domain object, leading to runtime errors like the following one:
|
|
|
|
'ERROR: The VM "<libvirt.virDomain object at 0x7fad04208650>" is not present'
|
|
|
|
* qemu-img info needs -U flag on running VMs
|
|
|
|
When getting VM disks informations on a running VM, the following error
|
|
occured:
|
|
|
|
The minion function caused an exception: Traceback (most recent call last):
|
|
File "/usr/lib/python3.6/site-packages/salt/minion.py", line 1673, in _thread_return
|
|
return_data = minion_instance.executors[fname](opts, data, func, args, kwargs)
|
|
File "/usr/lib/python3.6/site-packages/salt/executors/direct_call.py", line 12, in execute
|
|
return func(*args, **kwargs)
|
|
File "/usr/lib/python3.6/site-packages/salt/modules/virt.py", line 2411, in full_info
|
|
'vm_info': vm_info()}
|
|
File "/usr/lib/python3.6/site-packages/salt/modules/virt.py", line 2020, in vm_info
|
|
info[domain.name()] = _info(domain)
|
|
File "/usr/lib/python3.6/site-packages/salt/modules/virt.py", line 2004, in _info
|
|
'disks': _get_disks(dom),
|
|
File "/usr/lib/python3.6/site-packages/salt/modules/virt.py", line 465, in _get_disks
|
|
output = _parse_qemu_img_info(qemu_output)
|
|
File "/usr/lib/python3.6/site-packages/salt/modules/virt.py", line 287, in _parse_qemu_img_info
|
|
raw_infos = salt.utils.json.loads(info)
|
|
File "/usr/lib/python3.6/site-packages/salt/utils/json.py", line 92, in loads
|
|
return json_module.loads(s, **kwargs)
|
|
File "/usr/lib64/python3.6/json/__init__.py", line 354, in loads
|
|
return _default_decoder.decode(s)
|
|
File "/usr/lib64/python3.6/json/decoder.py", line 339, in decode
|
|
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
|
File "/usr/lib64/python3.6/json/decoder.py", line 357, in raw_decode
|
|
raise JSONDecodeError("Expecting value", s, err.value) from None
|
|
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
|
|
|
|
This is due to the fact that qemu-img can't get infos on a disk that is
|
|
already used like by a running VM. Using the qemu-img -U flag gets it
|
|
running in all cases.
|
|
---
|
|
salt/modules/virt.py | 10 +-
|
|
tests/unit/modules/test_virt.py | 242 +++++++++++++++++---------------
|
|
2 files changed, 132 insertions(+), 120 deletions(-)
|
|
|
|
diff --git a/salt/modules/virt.py b/salt/modules/virt.py
|
|
index 96c17bd60b..d01b6c3f1e 100644
|
|
--- a/salt/modules/virt.py
|
|
+++ b/salt/modules/virt.py
|
|
@@ -331,7 +331,7 @@ def _get_uuid(dom):
|
|
|
|
salt '*' virt.get_uuid <domain>
|
|
'''
|
|
- return ElementTree.fromstring(get_xml(dom)).find('uuid').text
|
|
+ return ElementTree.fromstring(dom.XMLDesc(0)).find('uuid').text
|
|
|
|
|
|
def _get_on_poweroff(dom):
|
|
@@ -344,7 +344,7 @@ def _get_on_poweroff(dom):
|
|
|
|
salt '*' virt.get_on_restart <domain>
|
|
'''
|
|
- node = ElementTree.fromstring(get_xml(dom)).find('on_poweroff')
|
|
+ node = ElementTree.fromstring(dom.XMLDesc(0)).find('on_poweroff')
|
|
return node.text if node is not None else ''
|
|
|
|
|
|
@@ -358,7 +358,7 @@ def _get_on_reboot(dom):
|
|
|
|
salt '*' virt.get_on_reboot <domain>
|
|
'''
|
|
- node = ElementTree.fromstring(get_xml(dom)).find('on_reboot')
|
|
+ node = ElementTree.fromstring(dom.XMLDesc(0)).find('on_reboot')
|
|
return node.text if node is not None else ''
|
|
|
|
|
|
@@ -372,7 +372,7 @@ def _get_on_crash(dom):
|
|
|
|
salt '*' virt.get_on_crash <domain>
|
|
'''
|
|
- node = ElementTree.fromstring(get_xml(dom)).find('on_crash')
|
|
+ node = ElementTree.fromstring(dom.XMLDesc(0)).find('on_crash')
|
|
return node.text if node is not None else ''
|
|
|
|
|
|
@@ -458,7 +458,7 @@ def _get_disks(dom):
|
|
if driver is not None and driver.get('type') == 'qcow2':
|
|
try:
|
|
stdout = subprocess.Popen(
|
|
- ['qemu-img', 'info', '--output', 'json', '--backing-chain', disk['file']],
|
|
+ ['qemu-img', 'info', '-U', '--output', 'json', '--backing-chain', disk['file']],
|
|
shell=False,
|
|
stdout=subprocess.PIPE).communicate()[0]
|
|
qemu_output = salt.utils.stringutils.to_str(stdout)
|
|
diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py
|
|
index e644e62452..4d20e998d8 100644
|
|
--- a/tests/unit/modules/test_virt.py
|
|
+++ b/tests/unit/modules/test_virt.py
|
|
@@ -81,7 +81,9 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
mock_domain.XMLDesc.return_value = xml # pylint: disable=no-member
|
|
|
|
# Return state as shutdown
|
|
- mock_domain.info.return_value = [4, 0, 0, 0] # pylint: disable=no-member
|
|
+ mock_domain.info.return_value = [4, 2048 * 1024, 1024 * 1024, 2, 1234] # pylint: disable=no-member
|
|
+ mock_domain.ID.return_value = 1
|
|
+ mock_domain.name.return_value = name
|
|
return mock_domain
|
|
|
|
def test_disk_profile_merge(self):
|
|
@@ -1394,49 +1396,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
re.match('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$',
|
|
interface_attrs['mac'], re.I))
|
|
|
|
- def test_get_graphics(self):
|
|
- '''
|
|
- Test virt.get_graphics()
|
|
- '''
|
|
- xml = '''<domain type='kvm' id='7'>
|
|
- <name>test-vm</name>
|
|
- <devices>
|
|
- <graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
|
|
- <listen type='address' address='0.0.0.0'/>
|
|
- </graphics>
|
|
- </devices>
|
|
- </domain>
|
|
- '''
|
|
- self.set_mock_vm("test-vm", xml)
|
|
-
|
|
- graphics = virt.get_graphics('test-vm')
|
|
- self.assertEqual('vnc', graphics['type'])
|
|
- self.assertEqual('5900', graphics['port'])
|
|
- self.assertEqual('0.0.0.0', graphics['listen'])
|
|
-
|
|
- def test_get_nics(self):
|
|
- '''
|
|
- Test virt.get_nics()
|
|
- '''
|
|
- xml = '''<domain type='kvm' id='7'>
|
|
- <name>test-vm</name>
|
|
- <devices>
|
|
- <interface type='bridge'>
|
|
- <mac address='ac:de:48:b6:8b:59'/>
|
|
- <source bridge='br0'/>
|
|
- <model type='virtio'/>
|
|
- <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
- </interface>
|
|
- </devices>
|
|
- </domain>
|
|
- '''
|
|
- self.set_mock_vm("test-vm", xml)
|
|
-
|
|
- nics = virt.get_nics('test-vm')
|
|
- nic = nics[list(nics)[0]]
|
|
- self.assertEqual('bridge', nic['type'])
|
|
- self.assertEqual('ac:de:48:b6:8b:59', nic['mac'])
|
|
-
|
|
def test_parse_qemu_img_info(self):
|
|
'''
|
|
Make sure that qemu-img info output is properly parsed
|
|
@@ -1558,77 +1517,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
],
|
|
}, virt._parse_qemu_img_info(qemu_infos))
|
|
|
|
- def test_get_disks(self):
|
|
- '''
|
|
- Test virt.get_disks()
|
|
- '''
|
|
- xml = '''<domain type='kvm' id='7'>
|
|
- <name>test-vm</name>
|
|
- <devices>
|
|
- <disk type='file' device='disk'>
|
|
- <driver name='qemu' type='qcow2'/>
|
|
- <source file='/disks/test.qcow2'/>
|
|
- <target dev='vda' bus='virtio'/>
|
|
- </disk>
|
|
- <disk type='file' device='cdrom'>
|
|
- <driver name='qemu' type='raw'/>
|
|
- <source file='/disks/test-cdrom.iso'/>
|
|
- <target dev='hda' bus='ide'/>
|
|
- <readonly/>
|
|
- </disk>
|
|
- </devices>
|
|
- </domain>
|
|
- '''
|
|
- self.set_mock_vm("test-vm", xml)
|
|
-
|
|
- qemu_infos = '''[{
|
|
- "virtual-size": 25769803776,
|
|
- "filename": "/disks/test.qcow2",
|
|
- "cluster-size": 65536,
|
|
- "format": "qcow2",
|
|
- "actual-size": 217088,
|
|
- "format-specific": {
|
|
- "type": "qcow2",
|
|
- "data": {
|
|
- "compat": "1.1",
|
|
- "lazy-refcounts": false,
|
|
- "refcount-bits": 16,
|
|
- "corrupt": false
|
|
- }
|
|
- },
|
|
- "full-backing-filename": "/disks/mybacking.qcow2",
|
|
- "backing-filename": "mybacking.qcow2",
|
|
- "dirty-flag": false
|
|
- },
|
|
- {
|
|
- "virtual-size": 25769803776,
|
|
- "filename": "/disks/mybacking.qcow2",
|
|
- "cluster-size": 65536,
|
|
- "format": "qcow2",
|
|
- "actual-size": 393744384,
|
|
- "format-specific": {
|
|
- "type": "qcow2",
|
|
- "data": {
|
|
- "compat": "1.1",
|
|
- "lazy-refcounts": false,
|
|
- "refcount-bits": 16,
|
|
- "corrupt": false
|
|
- }
|
|
- },
|
|
- "dirty-flag": false
|
|
- }]'''
|
|
-
|
|
- self.mock_popen.communicate.return_value = [qemu_infos] # pylint: disable=no-member
|
|
- disks = virt.get_disks('test-vm')
|
|
- disk = disks.get('vda')
|
|
- self.assertEqual('/disks/test.qcow2', disk['file'])
|
|
- self.assertEqual('disk', disk['type'])
|
|
- self.assertEqual('/disks/mybacking.qcow2', disk['backing file']['file'])
|
|
- cdrom = disks.get('hda')
|
|
- self.assertEqual('/disks/test-cdrom.iso', cdrom['file'])
|
|
- self.assertEqual('cdrom', cdrom['type'])
|
|
- self.assertFalse('backing file' in cdrom.keys())
|
|
-
|
|
@patch('salt.modules.virt.stop', return_value=True)
|
|
@patch('salt.modules.virt.undefine')
|
|
@patch('os.remove')
|
|
@@ -2994,3 +2882,127 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
virt.volume_delete('default', 'missing')
|
|
virt.volume_delete('missing', 'test_volume')
|
|
self.assertEqual(mock_delete.call_count, 2)
|
|
+
|
|
+ def test_full_info(self):
|
|
+ '''
|
|
+ Test virt.full_info
|
|
+ '''
|
|
+ xml = '''<domain type='kvm' id='7'>
|
|
+ <uuid>28deee33-4859-4f23-891c-ee239cffec94</uuid>
|
|
+ <name>test-vm</name>
|
|
+ <on_poweroff>destroy</on_poweroff>
|
|
+ <on_reboot>restart</on_reboot>
|
|
+ <on_crash>destroy</on_crash>
|
|
+ <devices>
|
|
+ <disk type='file' device='disk'>
|
|
+ <driver name='qemu' type='qcow2'/>
|
|
+ <source file='/disks/test.qcow2'/>
|
|
+ <target dev='vda' bus='virtio'/>
|
|
+ </disk>
|
|
+ <disk type='file' device='cdrom'>
|
|
+ <driver name='qemu' type='raw'/>
|
|
+ <source file='/disks/test-cdrom.iso'/>
|
|
+ <target dev='hda' bus='ide'/>
|
|
+ <readonly/>
|
|
+ </disk>
|
|
+ <interface type='bridge'>
|
|
+ <mac address='ac:de:48:b6:8b:59'/>
|
|
+ <source bridge='br0'/>
|
|
+ <model type='virtio'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ <graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
|
|
+ <listen type='address' address='0.0.0.0'/>
|
|
+ </graphics>
|
|
+ </devices>
|
|
+ </domain>
|
|
+ '''
|
|
+ self.set_mock_vm("test-vm", xml)
|
|
+
|
|
+ qemu_infos = '''[{
|
|
+ "virtual-size": 25769803776,
|
|
+ "filename": "/disks/test.qcow2",
|
|
+ "cluster-size": 65536,
|
|
+ "format": "qcow2",
|
|
+ "actual-size": 217088,
|
|
+ "format-specific": {
|
|
+ "type": "qcow2",
|
|
+ "data": {
|
|
+ "compat": "1.1",
|
|
+ "lazy-refcounts": false,
|
|
+ "refcount-bits": 16,
|
|
+ "corrupt": false
|
|
+ }
|
|
+ },
|
|
+ "full-backing-filename": "/disks/mybacking.qcow2",
|
|
+ "backing-filename": "mybacking.qcow2",
|
|
+ "dirty-flag": false
|
|
+ },
|
|
+ {
|
|
+ "virtual-size": 25769803776,
|
|
+ "filename": "/disks/mybacking.qcow2",
|
|
+ "cluster-size": 65536,
|
|
+ "format": "qcow2",
|
|
+ "actual-size": 393744384,
|
|
+ "format-specific": {
|
|
+ "type": "qcow2",
|
|
+ "data": {
|
|
+ "compat": "1.1",
|
|
+ "lazy-refcounts": false,
|
|
+ "refcount-bits": 16,
|
|
+ "corrupt": false
|
|
+ }
|
|
+ },
|
|
+ "dirty-flag": false
|
|
+ }]'''
|
|
+
|
|
+ self.mock_popen.communicate.return_value = [qemu_infos] # pylint: disable=no-member
|
|
+
|
|
+ self.mock_conn.getInfo = MagicMock(return_value=['x86_64', 4096, 8, 2712, 1, 2, 4, 2])
|
|
+
|
|
+ actual = virt.full_info()
|
|
+
|
|
+ # Test the hypervisor infos
|
|
+ self.assertEqual(2816, actual['freemem'])
|
|
+ self.assertEqual(6, actual['freecpu'])
|
|
+ self.assertEqual(4, actual['node_info']['cpucores'])
|
|
+ self.assertEqual(2712, actual['node_info']['cpumhz'])
|
|
+ self.assertEqual('x86_64', actual['node_info']['cpumodel'])
|
|
+ self.assertEqual(8, actual['node_info']['cpus'])
|
|
+ self.assertEqual(2, actual['node_info']['cputhreads'])
|
|
+ self.assertEqual(1, actual['node_info']['numanodes'])
|
|
+ self.assertEqual(4096, actual['node_info']['phymemory'])
|
|
+ self.assertEqual(2, actual['node_info']['sockets'])
|
|
+
|
|
+ # Test the vm_info output:
|
|
+ self.assertEqual(2, actual['vm_info']['test-vm']['cpu'])
|
|
+ self.assertEqual(1234, actual['vm_info']['test-vm']['cputime'])
|
|
+ self.assertEqual(1024 * 1024, actual['vm_info']['test-vm']['mem'])
|
|
+ self.assertEqual(2048 * 1024, actual['vm_info']['test-vm']['maxMem'])
|
|
+ self.assertEqual('shutdown', actual['vm_info']['test-vm']['state'])
|
|
+ self.assertEqual('28deee33-4859-4f23-891c-ee239cffec94', actual['vm_info']['test-vm']['uuid'])
|
|
+ self.assertEqual('destroy', actual['vm_info']['test-vm']['on_crash'])
|
|
+ self.assertEqual('restart', actual['vm_info']['test-vm']['on_reboot'])
|
|
+ self.assertEqual('destroy', actual['vm_info']['test-vm']['on_poweroff'])
|
|
+
|
|
+ # Test the nics
|
|
+ nic = actual['vm_info']['test-vm']['nics']['ac:de:48:b6:8b:59']
|
|
+ self.assertEqual('bridge', nic['type'])
|
|
+ self.assertEqual('ac:de:48:b6:8b:59', nic['mac'])
|
|
+
|
|
+ # Test the disks
|
|
+ disks = actual['vm_info']['test-vm']['disks']
|
|
+ disk = disks.get('vda')
|
|
+ self.assertEqual('/disks/test.qcow2', disk['file'])
|
|
+ self.assertEqual('disk', disk['type'])
|
|
+ self.assertEqual('/disks/mybacking.qcow2', disk['backing file']['file'])
|
|
+ cdrom = disks.get('hda')
|
|
+ self.assertEqual('/disks/test-cdrom.iso', cdrom['file'])
|
|
+ self.assertEqual('cdrom', cdrom['type'])
|
|
+ self.assertFalse('backing file' in cdrom.keys())
|
|
+
|
|
+ # Test the graphics
|
|
+ graphics = actual['vm_info']['test-vm']['graphics']
|
|
+ self.assertEqual('vnc', graphics['type'])
|
|
+ self.assertEqual('5900', graphics['port'])
|
|
+ self.assertEqual('0.0.0.0', graphics['listen'])
|
|
--
|
|
2.20.1
|
|
|
|
|