Skip to content

Commit

Permalink
Merge pull request #55346 from cbosdo/master-pool-capabilities
Browse files Browse the repository at this point in the history
Add virt.pool_capabilities function
  • Loading branch information
dwoz authored Dec 27, 2019
2 parents d5e0adb + 4843cde commit 026f136
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
158 changes: 158 additions & 0 deletions salt/modules/virt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4523,6 +4523,164 @@ def network_set_autostart(name, state='on', **kwargs):
conn.close()


def _parse_pools_caps(doc):
'''
Parse libvirt pool capabilities XML
'''
def _parse_pool_caps(pool):
pool_caps = {
'name': pool.get('type'),
'supported': pool.get('supported', 'no') == 'yes'
}
for option_kind in ['pool', 'vol']:
options = {}
default_format_node = pool.find('{0}Options/defaultFormat'.format(option_kind))
if default_format_node is not None:
options['default_format'] = default_format_node.get('type')
options_enums = {enum.get('name'): [value.text for value in enum.findall('value')]
for enum in pool.findall('{0}Options/enum'.format(option_kind))}
if options_enums:
options.update(options_enums)
if options:
if 'options' not in pool_caps:
pool_caps['options'] = {}
kind = option_kind if option_kind is not 'vol' else 'volume'
pool_caps['options'][kind] = options
return pool_caps

return [_parse_pool_caps(pool) for pool in doc.findall('pool')]


def pool_capabilities(**kwargs):
'''
Return the hypervisor connection storage pool capabilities.
The returned data are either directly extracted from libvirt or computed.
In the latter case some pool types could be listed as supported while they
are not. To distinguish between the two cases, check the value of the ``computed`` property.
:param connection: libvirt connection URI, overriding defaults
:param username: username to connect with, overriding defaults
:param password: password to connect with, overriding defaults
.. versionadded:: Neon
CLI Example:
.. code-block:: bash
salt '*' virt.pool_capabilities
'''
try:
conn = __get_conn(**kwargs)
has_pool_capabilities = bool(getattr(conn, 'getStoragePoolCapabilities', None))
if has_pool_capabilities:
caps = ElementTree.fromstring(conn.getStoragePoolCapabilities())
pool_types = _parse_pools_caps(caps)
else:
# Compute reasonable values
all_hypervisors = ['xen', 'kvm', 'bhyve']
images_formats = ['none', 'raw', 'dir', 'bochs', 'cloop', 'dmg', 'iso', 'vpc', 'vdi',
'fat', 'vhd', 'ploop', 'cow', 'qcow', 'qcow2', 'qed', 'vmdk']
common_drivers = [
{
'name': 'fs',
'default_source_format': 'auto',
'source_formats': ['auto', 'ext2', 'ext3', 'ext4', 'ufs', 'iso9660', 'udf', 'gfs', 'gfs2',
'vfat', 'hfs+', 'xfs', 'ocfs2'],
'default_target_format': 'raw',
'target_formats': images_formats
},
{
'name': 'dir',
'default_target_format': 'raw',
'target_formats': images_formats
},
{'name': 'iscsi'},
{'name': 'scsi'},
{
'name': 'logical',
'default_source_format': 'lvm2',
'source_formats': ['unknown', 'lvm2'],
},
{
'name': 'netfs',
'default_source_format': 'auto',
'source_formats': ['auto', 'nfs', 'glusterfs', 'cifs'],
'default_target_format': 'raw',
'target_formats': images_formats
},
{
'name': 'disk',
'default_source_format': 'unknown',
'source_formats': ['unknown', 'dos', 'dvh', 'gpt', 'mac', 'bsd', 'pc98', 'sun', 'lvm2'],
'default_target_format': 'none',
'target_formats': ['none', 'linux', 'fat16', 'fat32', 'linux-swap', 'linux-lvm',
'linux-raid', 'extended']
},
{'name': 'mpath'},
{
'name': 'rbd',
'default_target_format': 'raw',
'target_formats': []
},
{
'name': 'sheepdog',
'version': 10000,
'hypervisors': ['kvm'],
'default_target_format': 'raw',
'target_formats': images_formats
},
{
'name': 'gluster',
'version': 1002000,
'hypervisors': ['kvm'],
'default_target_format': 'raw',
'target_formats': images_formats
},
{'name': 'zfs', 'version': 1002008, 'hypervisors': ['bhyve']},
{'name': 'iscsi-direct', 'version': 4007000, 'hypervisors': ['kvm', 'xen']}
]

libvirt_version = conn.getLibVersion()
hypervisor = get_hypervisor()

def _get_backend_output(backend):
output = {
'name': backend['name'],
'supported': (not backend.get('version') or libvirt_version >= backend['version']) and
hypervisor in backend.get('hypervisors', all_hypervisors),
'options': {
'pool': {
'default_format': backend.get('default_source_format'),
'sourceFormatType': backend.get('source_formats')
},
'volume': {
'default_format': backend.get('default_target_format'),
'targetFormatType': backend.get('target_formats')
}
}
}

# Cleanup the empty members to match the libvirt output
for option_kind in ['pool', 'volume']:
if not [value for value in output['options'][option_kind].values() if value is not None]:
del output['options'][option_kind]
if not output['options']:
del output['options']

return output
pool_types = [_get_backend_output(backend) for backend in common_drivers]
finally:
conn.close()

return {
'computed': not has_pool_capabilities,
'pool_types': pool_types,
}


def pool_define(name,
ptype,
target=None,
Expand Down
120 changes: 120 additions & 0 deletions tests/unit/modules/test_virt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3018,3 +3018,123 @@ def test_pool_update_password_create(self):
'password': 'c2VjcmV0'}))
self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml)
mock_secret.setValue.assert_called_once_with(b'secret')

def test_pool_capabilities(self):
'''
Test virt.pool_capabilities where libvirt has the pool-capabilities feature
'''
xml_caps = '''
<storagepoolCapabilities>
<pool type='disk' supported='yes'>
<poolOptions>
<defaultFormat type='unknown'/>
<enum name='sourceFormatType'>
<value>unknown</value>
<value>dos</value>
<value>dvh</value>
</enum>
</poolOptions>
<volOptions>
<defaultFormat type='none'/>
<enum name='targetFormatType'>
<value>none</value>
<value>linux</value>
</enum>
</volOptions>
</pool>
<pool type='iscsi' supported='yes'>
</pool>
<pool type='rbd' supported='yes'>
<volOptions>
<defaultFormat type='raw'/>
<enum name='targetFormatType'>
</enum>
</volOptions>
</pool>
<pool type='sheepdog' supported='no'>
</pool>
</storagepoolCapabilities>
'''
self.mock_conn.getStoragePoolCapabilities = MagicMock(return_value=xml_caps)

actual = virt.pool_capabilities()
self.assertEqual({
'computed': False,
'pool_types': [{
'name': 'disk',
'supported': True,
'options': {
'pool': {
'default_format': 'unknown',
'sourceFormatType': ['unknown', 'dos', 'dvh']
},
'volume': {
'default_format': 'none',
'targetFormatType': ['none', 'linux']
}
}
},
{
'name': 'iscsi',
'supported': True,
},
{
'name': 'rbd',
'supported': True,
'options': {
'volume': {
'default_format': 'raw',
'targetFormatType': []
}
}
},
{
'name': 'sheepdog',
'supported': False,
},
]}, actual)

@patch('salt.modules.virt.get_hypervisor', return_value='kvm')
def test_pool_capabilities_computed(self, mock_get_hypervisor):
'''
Test virt.pool_capabilities where libvirt doesn't have the pool-capabilities feature
'''
self.mock_conn.getLibVersion = MagicMock(return_value=4006000)
del self.mock_conn.getStoragePoolCapabilities

actual = virt.pool_capabilities()

self.assertTrue(actual['computed'])
backends = actual['pool_types']

# libvirt version matching check
self.assertFalse([backend for backend in backends if backend['name'] == 'iscsi-direct'][0]['supported'])
self.assertTrue([backend for backend in backends if backend['name'] == 'gluster'][0]['supported'])
self.assertFalse([backend for backend in backends if backend['name'] == 'zfs'][0]['supported'])

# test case matching other hypervisors
mock_get_hypervisor.return_value = 'xen'
backends = virt.pool_capabilities()['pool_types']
self.assertFalse([backend for backend in backends if backend['name'] == 'gluster'][0]['supported'])

mock_get_hypervisor.return_value = 'bhyve'
backends = virt.pool_capabilities()['pool_types']
self.assertFalse([backend for backend in backends if backend['name'] == 'gluster'][0]['supported'])
self.assertTrue([backend for backend in backends if backend['name'] == 'zfs'][0]['supported'])

# Test options output
self.assertNotIn('options', [backend for backend in backends if backend['name'] == 'iscsi'][0])
self.assertNotIn('pool', [backend for backend in backends if backend['name'] == 'dir'][0]['options'])
self.assertNotIn('volume', [backend for backend in backends if backend['name'] == 'logical'][0]['options'])
self.assertEqual({
'pool': {
'default_format': 'auto',
'sourceFormatType': ['auto', 'nfs', 'glusterfs', 'cifs']
},
'volume': {
'default_format': 'raw',
'targetFormatType': ['none', 'raw', 'dir', 'bochs', 'cloop', 'dmg', 'iso', 'vpc', 'vdi',
'fat', 'vhd', 'ploop', 'cow', 'qcow', 'qcow2', 'qed', 'vmdk']
}
},
[backend for backend in backends if backend['name'] == 'netfs'][0]['options'])

0 comments on commit 026f136

Please sign in to comment.