diff --git a/.gitignore b/.gitignore index e5be3945..10412da9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Packages *.egg +*.eggs *.egg-info dist build diff --git a/etc/nova/rootwrap.d/lxd.filters b/etc/nova/rootwrap.d/lxd.filters index 7f030200..e42168d1 100644 --- a/etc/nova/rootwrap.d/lxd.filters +++ b/etc/nova/rootwrap.d/lxd.filters @@ -3,4 +3,5 @@ [Filters] zfs: CommandFilter, zfs, root +zpool: CommandFilter, zpool, root btrfs: CommandFilter, btrfs, root diff --git a/nova/tests/unit/virt/lxd/test_driver.py b/nova/tests/unit/virt/lxd/test_driver.py index e9957c71..832a0ce1 100644 --- a/nova/tests/unit/virt/lxd/test_driver.py +++ b/nova/tests/unit/virt/lxd/test_driver.py @@ -1040,8 +1040,84 @@ class LXDDriverTest(test.NoDBTestCase): '\n'), meminfo, ] - + lxd_config = { + 'environment': { + 'storage': 'dir', + }, + 'config': {} + } lxd_driver = driver.LXDDriver(None) + lxd_driver.client = mock.MagicMock() + lxd_driver.client.host_info = lxd_config + value = lxd_driver.get_available_resource(None) + # This is funky, but json strings make for fragile tests. + value['cpu_info'] = json.loads(value['cpu_info']) + + self.assertEqual(expected, value) + + @mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname')) + @mock.patch('nova.virt.lxd.driver.open') + @mock.patch.object(driver.utils, 'execute') + def test_get_available_resource_zfs(self, execute, open): + expected = { + 'cpu_info': { + "features": "fake flag goes here", + "model": "Fake CPU", + "topology": {"sockets": "10", "threads": "4", "cores": "5"}, + "arch": "x86_64", "vendor": "FakeVendor" + }, + 'hypervisor_hostname': 'fake_hostname', + 'hypervisor_type': 'lxd', + 'hypervisor_version': '011', + 'local_gb': 2222, + 'local_gb_used': 200, + 'memory_mb': 10000, + 'memory_mb_used': 8000, + 'numa_topology': None, + 'supported_instances': [ + ('i686', 'lxd', 'exe'), + ('x86_64', 'lxd', 'exe'), + ('i686', 'lxc', 'exe'), + ('x86_64', 'lxc', 'exe')], + 'vcpus': 200, + 'vcpus_used': 0} + + execute.side_effect = [ + ('Model name: Fake CPU\n' + 'Vendor ID: FakeVendor\n' + 'Socket(s): 10\n' + '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) + ] + + meminfo = mock.MagicMock() + meminfo.__enter__.return_value = six.moves.cStringIO( + 'MemTotal: 10240000 kB\n' + 'MemFree: 2000000 kB\n' + 'Buffers: 24000 kB\n' + 'Cached: 24000 kB\n') + + open.side_effect = [ + six.moves.cStringIO('flags: fake flag goes here\n' + 'processor: 2\n' + '\n'), + meminfo, + ] + lxd_config = { + 'environment': { + 'storage': 'zfs', + }, + 'config': { + 'storage.zfs_pool_name': 'lxd', + } + } + lxd_driver = driver.LXDDriver(None) + lxd_driver.client = mock.MagicMock() + lxd_driver.client.host_info = lxd_config value = lxd_driver.get_available_resource(None) # This is funky, but json strings make for fragile tests. value['cpu_info'] = json.loads(value['cpu_info']) diff --git a/nova/virt/lxd/driver.py b/nova/virt/lxd/driver.py index 92fa3eef..ef6a6b21 100644 --- a/nova/virt/lxd/driver.py +++ b/nova/virt/lxd/driver.py @@ -62,6 +62,7 @@ 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._ @@ -160,6 +161,28 @@ 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', + '-o', attribute, + '-H', pool, + run_as_root=True) + if err: + msg = _('Unable to parse zpool output.') + raise exception.NovaException(msg) + value = strutils.string_to_bytes('{}B'.format(value.strip()), + return_int=True) + return value + + total = _get_zpool_attribute('size') + used = _get_zpool_attribute('alloc') + available = _get_zpool_attribute('free') + return {'total': total, + 'available': available, + 'used': used} + + def _get_power_state(lxd_state): """Take a lxd state code and translate it to nova power state.""" state_map = [ @@ -858,7 +881,19 @@ class LXDDriver(driver.ComputeDriver): int(cpu_topology['threads'])) local_memory_info = _get_ram_usage() - local_disk_info = _get_fs_info(CONF.lxd.root_dir) + + lxd_config = self.client.host_info + + # NOTE(jamespage): ZFS storage report is very LXD 2.0.x + # centric and will need to be updated + # to support LXD storage pools + storage_driver = lxd_config['environment']['storage'] + if storage_driver == 'zfs': + local_disk_info = _get_zpool_info( + lxd_config['config']['storage.zfs_pool_name'] + ) + else: + local_disk_info = _get_fs_info(CONF.lxd.root_dir) data = { 'vcpus': vcpus,