Enable ZFS to work with datasets and all locales

This fixes two bugs (independently) to get nova-lxd to work with
datasets (pool/set) as well as pools (just 'pool').  It also, switches
the attributes to use the '-p' option with 'zfs list' and 'zpool list'
which returns the values in bytes rather than human readable format,
which is more useful in nova-lxd.

Change-Id: I2eb1b3ba7bd482e680933808414a3992a9a1feba
Closes-Bug: #1756900
Closes-Bug: #1757371
This commit is contained in:
Alex Kavanagh 2018-05-30 16:19:00 +01:00
parent bd4378a74f
commit 97cb7ea742
2 changed files with 62 additions and 15 deletions

View File

@ -1176,6 +1176,34 @@ class LXDDriverTest(test.NoDBTestCase):
self.assertEqual(expected, value)
@mock.patch.object(driver.utils, 'execute')
def test__get_zpool_info(self, execute):
# first test with a zpool; should make 3 calls to execute
execute.side_effect = [
('1\n', None),
('2\n', None),
('3\n', None)
]
expected = {
'total': 1,
'used': 2,
'available': 3,
}
self.assertEqual(expected, driver._get_zpool_info('lxd'))
# then test with a zfs dataset; should just be 2 calls
execute.reset_mock()
execute.side_effect = [
('10\n', None),
('20\n', None),
]
expected = {
'total': 30,
'used': 10,
'available': 20,
}
self.assertEqual(expected, driver._get_zpool_info('lxd/dataset'))
@mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname'))
@mock.patch('nova.virt.lxd.driver.open')
@mock.patch.object(driver.utils, 'execute')
@ -1210,9 +1238,9 @@ class LXDDriverTest(test.NoDBTestCase):
'Core(s) per socket: 5\n'
'Thread(s) per core: 4\n\n',
None),
('2.17T\n', None),
('200.4G\n', None),
('1.8T\n', None)
('2385940232273\n', None), # 2.17T
('215177861529\n', None), # 200.4G
('1979120929996\n', None) # 1.8T
]
meminfo = mock.MagicMock()

View File

@ -66,7 +66,6 @@ import psutil
from oslo_concurrency import lockutils
from nova.compute import task_states
from oslo_utils import excutils
from oslo_utils import strutils
from nova.virt import firewall
_ = i18n._
@ -192,23 +191,43 @@ def _get_fs_info(path):
'used': used}
def _get_zpool_info(pool):
"""Get free/used/total disk space in a zfs pool."""
def _get_zpool_attribute(attribute):
value, err = utils.execute('zpool', 'list',
def _get_zpool_info(pool_or_dataset):
"""Get the free/used/total diskspace in a zfs pool or dataset.
A dataset is distinguished by having a '/' in the string.
:param pool_or_dataset: The string name of the pool or dataset
:type pool_or_dataset: str
:returns: dictionary with keys 'total', 'available', 'used'
:rtype: Dict[str, int]
:raises: :class:`exception.NovaException`
:raises: :class:`oslo.concurrency.PorcessExecutionError`
:raises: :class:`OSError`
"""
def _get_zfs_attribute(cmd, attribute):
value, err = utils.execute(cmd, 'list',
'-o', attribute,
'-H', pool,
'-H',
'-p',
pool_or_dataset,
run_as_root=True)
if err:
msg = _('Unable to parse zpool output.')
msg = _('Unable to parse zfs output.')
raise exception.NovaException(msg)
value = strutils.string_to_bytes('{}B'.format(value.strip()),
return_int=True)
value = int(value.strip())
return value
total = _get_zpool_attribute('size')
used = _get_zpool_attribute('alloc')
available = _get_zpool_attribute('free')
if '/' in pool_or_dataset:
# it's a dataset:
# for zfs datasets we only have 'available' and 'used' and so need to
# construct the total from available and used.
used = _get_zfs_attribute('zfs', 'used')
available = _get_zfs_attribute('zfs', 'available')
total = available + used
else:
# otherwise it's a zpool
total = _get_zfs_attribute('zpool', 'size')
used = _get_zfs_attribute('zpool', 'alloc')
available = _get_zfs_attribute('zpool', 'free')
return {'total': total,
'available': available,
'used': used}