virt module: extract get_nics, get_disks and get_graphics

Extract the code of get_nics, get_disks and get_graphics into private
functions providing the same feature with a libvirt domain as parameter.

The rationale behind this change is to allow reducing the number of
libvirt connections opened during some calls.
This commit is contained in:
Cédric Bosdonnat 2018-04-19 11:32:12 +02:00
parent 2dfdc4eb4b
commit 627b2e2594
No known key found for this signature in database
GPG Key ID: 743CCED3EDD1578D
2 changed files with 167 additions and 131 deletions

View File

@ -278,6 +278,114 @@ def _parse_qemu_img_info(info):
return '\n'.join(output)
def _get_nics(dom):
'''
Get domain network interfaces from a libvirt domain object.
'''
nics = {}
doc = minidom.parse(_StringIO(dom.getXMLDesc(0)))
for node in doc.getElementsByTagName('devices'):
i_nodes = node.getElementsByTagName('interface')
for i_node in i_nodes:
nic = {}
nic['type'] = i_node.getAttribute('type')
for v_node in i_node.getElementsByTagName('*'):
if v_node.tagName == 'mac':
nic['mac'] = v_node.getAttribute('address')
if v_node.tagName == 'model':
nic['model'] = v_node.getAttribute('type')
if v_node.tagName == 'target':
nic['target'] = v_node.getAttribute('dev')
# driver, source, and match can all have optional attributes
if re.match('(driver|source|address)', v_node.tagName):
temp = {}
for key, value in v_node.attributes.items():
temp[key] = value
nic[six.text_type(v_node.tagName)] = temp
# virtualport needs to be handled separately, to pick up the
# type attribute of the virtualport itself
if v_node.tagName == 'virtualport':
temp = {}
temp['type'] = v_node.getAttribute('type')
for key, value in v_node.attributes.items():
temp[key] = value
nic['virtualport'] = temp
if 'mac' not in nic:
continue
nics[nic['mac']] = nic
return nics
def _get_graphics(dom):
'''
Get domain graphics from a libvirt domain object.
'''
out = {'autoport': 'None',
'keymap': 'None',
'listen': 'None',
'port': 'None',
'type': 'None'}
xml = dom.getXMLDesc(0)
ssock = _StringIO(xml)
doc = minidom.parse(ssock)
for node in doc.getElementsByTagName('domain'):
g_nodes = node.getElementsByTagName('graphics')
for g_node in g_nodes:
for key, value in g_node.attributes.items():
out[key] = value
return out
def _get_disks(dom):
'''
Get domain disks from a libvirt domain object.
'''
disks = {}
doc = minidom.parse(_StringIO(dom.getXMLDesc(0)))
for elem in doc.getElementsByTagName('disk'):
sources = elem.getElementsByTagName('source')
targets = elem.getElementsByTagName('target')
if sources:
source = sources[0]
else:
continue
if targets:
target = targets[0]
else:
continue
if target.hasAttribute('dev'):
qemu_target = ''
if source.hasAttribute('file'):
qemu_target = source.getAttribute('file')
elif source.hasAttribute('dev'):
qemu_target = source.getAttribute('dev')
elif source.hasAttribute('protocol') and \
source.hasAttribute('name'): # For rbd network
qemu_target = '{0}:{1}'.format(
source.getAttribute('protocol'),
source.getAttribute('name'))
if qemu_target:
disks[target.getAttribute('dev')] = {
'file': qemu_target,
'type': elem.getAttribute('device')}
for dev in disks:
try:
hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
if hypervisor not in ['qemu', 'kvm']:
break
stdout = subprocess.Popen(
['qemu-img', 'info', disks[dev]['file']],
shell=False,
stdout=subprocess.PIPE).communicate()[0]
qemu_output = salt.utils.stringutils.to_str(stdout)
output = _parse_qemu_img_info(qemu_output)
disks[dev].update(salt.utils.yaml.safe_load(output))
except TypeError:
disks[dev].update({'image': 'Does not exist'})
return disks
def _libvirt_creds():
'''
Returns the user and group that the disk images should be owned by
@ -1106,38 +1214,7 @@ def get_nics(vm_):
salt '*' virt.get_nics <domain>
'''
nics = {}
doc = minidom.parse(_StringIO(get_xml(vm_)))
for node in doc.getElementsByTagName('devices'):
i_nodes = node.getElementsByTagName('interface')
for i_node in i_nodes:
nic = {}
nic['type'] = i_node.getAttribute('type')
for v_node in i_node.getElementsByTagName('*'):
if v_node.tagName == 'mac':
nic['mac'] = v_node.getAttribute('address')
if v_node.tagName == 'model':
nic['model'] = v_node.getAttribute('type')
if v_node.tagName == 'target':
nic['target'] = v_node.getAttribute('dev')
# driver, source, and match can all have optional attributes
if re.match('(driver|source|address)', v_node.tagName):
temp = {}
for key, value in v_node.attributes.items():
temp[key] = value
nic[six.text_type(v_node.tagName)] = temp
# virtualport needs to be handled separately, to pick up the
# type attribute of the virtualport itself
if v_node.tagName == 'virtualport':
temp = {}
temp['type'] = v_node.getAttribute('type')
for key, value in v_node.attributes.items():
temp[key] = value
nic['virtualport'] = temp
if 'mac' not in nic:
continue
nics[nic['mac']] = nic
return nics
return _get_nics(_get_domain(vm_))
def get_macs(vm_):
@ -1170,20 +1247,7 @@ def get_graphics(vm_):
salt '*' virt.get_graphics <domain>
'''
out = {'autoport': 'None',
'keymap': 'None',
'listen': 'None',
'port': 'None',
'type': 'None'}
xml = get_xml(vm_)
ssock = _StringIO(xml)
doc = minidom.parse(ssock)
for node in doc.getElementsByTagName('domain'):
g_nodes = node.getElementsByTagName('graphics')
for g_node in g_nodes:
for key, value in g_node.attributes.items():
out[key] = value
return out
return _get_graphics(_get_domain(vm_))
def get_disks(vm_):
@ -1196,50 +1260,7 @@ def get_disks(vm_):
salt '*' virt.get_disks <domain>
'''
disks = {}
doc = minidom.parse(_StringIO(get_xml(vm_)))
for elem in doc.getElementsByTagName('disk'):
sources = elem.getElementsByTagName('source')
targets = elem.getElementsByTagName('target')
if sources:
source = sources[0]
else:
continue
if targets:
target = targets[0]
else:
continue
if target.hasAttribute('dev'):
qemu_target = ''
if source.hasAttribute('file'):
qemu_target = source.getAttribute('file')
elif source.hasAttribute('dev'):
qemu_target = source.getAttribute('dev')
elif source.hasAttribute('protocol') and \
source.hasAttribute('name'): # For rbd network
qemu_target = '{0}:{1}'.format(
source.getAttribute('protocol'),
source.getAttribute('name'))
if qemu_target:
disks[target.getAttribute('dev')] = {
'file': qemu_target,
'type': elem.getAttribute('device')}
for dev in disks:
try:
hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
if hypervisor not in ['qemu', 'kvm']:
break
stdout = subprocess.Popen(
['qemu-img', 'info', disks[dev]['file']],
shell=False,
stdout=subprocess.PIPE).communicate()[0]
qemu_output = salt.utils.stringutils.to_str(stdout)
output = _parse_qemu_img_info(qemu_output)
disks[dev].update(salt.utils.yaml.safe_load(output))
except TypeError:
disks[dev].update({'image': 'Does not exist'})
return disks
return _get_disks(_get_domain(vm_))
def setmem(vm_, memory, config=False):

View File

@ -31,15 +31,26 @@ class LibvirtMock(MagicMock):
class VirtTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
self.mock_libvirt = LibvirtMock()
self.mock_conn = MagicMock()
self.mock_libvirt.openAuth.return_value = self.mock_conn
self.addCleanup(delattr, self, 'mock_libvirt')
self.addCleanup(delattr, self, 'mock_conn')
loader_globals = {
'__salt__': {
'config.get': config.get,
'config.option': config.option,
},
'libvirt': LibvirtMock()
'libvirt': self.mock_libvirt
}
return {virt: loader_globals, config: loader_globals}
def set_mock_vm(self, name, xml):
self.mock_conn.listDefinedDomains.return_value = [name]
mock_domain = MagicMock()
self.mock_conn.lookupByName.return_value = mock_domain
mock_domain.getXMLDesc.return_value = xml
def test_boot_default_dev(self):
diskp = virt._disk_profile('default', 'kvm')
nicp = virt._nic_profile('default', 'kvm')
@ -481,7 +492,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
interface_attrs['mac'], re.I))
def test_get_graphics(self):
get_xml_mock = MagicMock(return_value='''<domain type='kvm' id='7'>
xml = '''<domain type='kvm' id='7'>
<name>test-vm</name>
<devices>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
@ -489,15 +500,16 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</graphics>
</devices>
</domain>
''')
with patch.object(virt, 'get_xml', get_xml_mock):
graphics = virt.get_graphics('test-vm')
self.assertEqual('vnc', graphics['type'])
self.assertEqual('5900', graphics['port'])
self.assertEqual('0.0.0.0', graphics['listen'])
'''
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):
get_xml_mock = MagicMock(return_value='''<domain type='kvm' id='7'>
xml = '''<domain type='kvm' id='7'>
<name>test-vm</name>
<devices>
<interface type='bridge'>
@ -508,17 +520,18 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</interface>
</devices>
</domain>
''')
with patch.object(virt, 'get_xml', get_xml_mock):
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'])
'''
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'])
@patch('subprocess.Popen')
@patch('subprocess.Popen.communicate', return_value="")
def test_get_disks(self, mock_communicate, mock_popen):
get_xml_mock = MagicMock(return_value='''<domain type='kvm' id='7'>
xml = '''<domain type='kvm' id='7'>
<name>test-vm</name>
<devices>
<disk type='file' device='disk'>
@ -534,15 +547,16 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</disk>
</devices>
</domain>
''')
with patch.object(virt, 'get_xml', get_xml_mock):
disks = virt.get_disks('test-vm')
disk = disks[list(disks)[0]]
self.assertEqual('/disks/test.qcow2', disk['file'])
self.assertEqual('disk', disk['type'])
cdrom = disks[list(disks)[1]]
self.assertEqual('/disks/test-cdrom.iso', cdrom['file'])
self.assertEqual('cdrom', cdrom['type'])
'''
self.set_mock_vm("test-vm", xml)
disks = virt.get_disks('test-vm')
disk = disks[list(disks)[0]]
self.assertEqual('/disks/test.qcow2', disk['file'])
self.assertEqual('disk', disk['type'])
cdrom = disks[list(disks)[1]]
self.assertEqual('/disks/test-cdrom.iso', cdrom['file'])
self.assertEqual('cdrom', cdrom['type'])
@patch('subprocess.Popen')
@patch('subprocess.Popen.communicate', return_value="")
@ -550,7 +564,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
@patch('salt.modules.virt.undefine')
@patch('os.remove')
def test_purge_default(self, mock_remove, mock_undefine, mock_stop, mock_communicate, mock_popen):
get_xml_mock = MagicMock(return_value='''<domain type='kvm' id='7'>
xml = '''<domain type='kvm' id='7'>
<name>test-vm</name>
<devices>
<disk type='file' device='disk'>
@ -566,13 +580,13 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</disk>
</devices>
</domain>
''')
mock = MagicMock(return_value={})
with patch.object(virt, 'get_xml', get_xml_mock):
res = virt.purge('test-vm')
self.assertTrue(res)
mock_remove.assert_any_call('/disks/test.qcow2')
mock_remove.assert_any_call('/disks/test-cdrom.iso')
'''
self.set_mock_vm("test-vm", xml)
res = virt.purge('test-vm')
self.assertTrue(res)
mock_remove.assert_any_call('/disks/test.qcow2')
mock_remove.assert_any_call('/disks/test-cdrom.iso')
@patch('subprocess.Popen')
@patch('subprocess.Popen.communicate', return_value="")
@ -580,7 +594,8 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
@patch('salt.modules.virt.undefine')
@patch('os.remove')
def test_purge_noremovable(self, mock_remove, mock_undefine, mock_stop, mock_communicate, mock_popen):
get_xml_mock = MagicMock(return_value='''<domain type='kvm' id='7'>
xml = '''<domain type='kvm' id='7'>
<name>test-vm</name>
<devices>
<disk type='file' device='disk'>
@ -602,13 +617,13 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</disk>
</devices>
</domain>
''')
mock = MagicMock(return_value={})
with patch.object(virt, 'get_xml', get_xml_mock):
res = virt.purge('test-vm', removables=False)
self.assertTrue(res)
mock_remove.assert_called_once()
mock_remove.assert_any_call('/disks/test.qcow2')
'''
self.set_mock_vm("test-vm", xml)
res = virt.purge('test-vm', removables=False)
self.assertTrue(res)
mock_remove.assert_called_once()
mock_remove.assert_any_call('/disks/test.qcow2')
def test_network(self):
xml_data = virt._gen_net_xml('network', 'main', 'bridge', 'openvswitch')