From 0774e313ddb58e33262a1ef916ecf6b34dde7b21 Mon Sep 17 00:00:00 2001 From: Paul Hummer Date: Tue, 5 Jul 2016 22:13:34 -0600 Subject: [PATCH] Modernize more of LXDDriver This includes adding an explicit cleanup_host (because it's important to acknowledge explicitly nothing is needed for cleanup), get_info using the new pylxd api, and more tests for list_instances. There were a number of methods that didn't need to be implemented by the lxd driver as they were identical to the parent class ComputeDriver. I also explicitly removed the "catch/log/raise" exception patterns, as I'd rather explicitly catch the exceptions and move on, or allow nova to handle the exception itself (and log it). Change-Id: I8c14da579d51c532e279d95d737b90580a790c03 --- nova/tests/unit/virt/lxd/test_driver.py | 46 ++++++++++++-- nova/tests/unit/virt/lxd/test_driver_api.py | 67 --------------------- nova/virt/lxd/driver.py | 63 +++++++++---------- 3 files changed, 69 insertions(+), 107 deletions(-) diff --git a/nova/tests/unit/virt/lxd/test_driver.py b/nova/tests/unit/virt/lxd/test_driver.py index c09e0066..8f5ee730 100644 --- a/nova/tests/unit/virt/lxd/test_driver.py +++ b/nova/tests/unit/virt/lxd/test_driver.py @@ -12,13 +12,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import collections + import mock +from nova import context from nova import exception from nova import test +from nova.compute import power_state +from nova.tests.unit import fake_instance from pylxd import exceptions as lxdcore_exceptions from nova.virt.lxd import driver +MockContainer = collections.namedtuple('Container', ['name']) +MockContainerState = collections.namedtuple( + 'ContainerState', ['status_code', 'memory']) + class LXDDriverTest(test.NoDBTestCase): """Tests for nova.virt.lxd.driver.LXDDriver.""" @@ -52,8 +61,35 @@ class LXDDriverTest(test.NoDBTestCase): lxd_driver = driver.LXDDriver(None) - self.assertRaises( - exception.HostNotFound, - lxd_driver.init_host, - None - ) + self.assertRaises(exception.HostNotFound, lxd_driver.init_host, None) + + def test_get_info(self): + container = mock.Mock() + container.state.return_value = MockContainerState( + 100, {'usage': 4000, 'usage_peak': 4500}) + self.client.containers.get.return_value = container + + ctx = context.get_admin_context() + instance = fake_instance.fake_instance_obj(ctx, name='test') + lxd_driver = driver.LXDDriver(None) + lxd_driver.init_host(None) + + info = lxd_driver.get_info(instance) + + self.assertEqual(power_state.RUNNING, info.state) + self.assertEqual(3, info.mem_kb) + self.assertEqual(4, info.max_mem_kb) + self.assertEqual(1, info.num_cpu) + self.assertEqual(0, info.cpu_time_ns) + + def test_list_instances(self): + self.client.containers.all.return_value = [ + MockContainer('mock-instance-1'), + MockContainer('mock-instance-2'), + ] + lxd_driver = driver.LXDDriver(None) + lxd_driver.init_host(None) + + instances = lxd_driver.list_instances() + + self.assertEqual(['mock-instance-1', 'mock-instance-2'], instances) diff --git a/nova/tests/unit/virt/lxd/test_driver_api.py b/nova/tests/unit/virt/lxd/test_driver_api.py index 3b65ed1f..b2a308d1 100644 --- a/nova/tests/unit/virt/lxd/test_driver_api.py +++ b/nova/tests/unit/virt/lxd/test_driver_api.py @@ -12,12 +12,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import collections import inspect import json import os import platform -from pylxd import exceptions as lxdcore_exceptions import ddt import mock @@ -27,20 +25,15 @@ from oslo_config import cfg from nova.compute import arch from nova.compute import hv_type -from nova.compute import power_state from nova.compute import vm_mode -from nova import exception from nova import test from nova.virt import fake -from nova.virt import hardware from nova.virt.lxd import driver from nova.virt.lxd import session from nova.virt.lxd import utils as container_dir import stubs -MockContainer = collections.namedtuple('Container', ['name']) - class LXDTestConfig(test.NoDBTestCase): @@ -70,13 +63,6 @@ class LXDTestDriver(test.NoDBTestCase): self.driver = driver.LXDDriver(mock.MagicMock()) self.driver.container_migrate = mock.MagicMock() - mock_client = mock.Mock() - mock_client.containers.all.return_value = [ - MockContainer('mock-instance-1'), - MockContainer('mock-instance-2'), - ] - self.connection.client = mock_client - def test_capabilities(self): self.assertFalse(self.connection.capabilities['has_imagecache']) self.assertFalse(self.connection.capabilities['supports_recreate']) @@ -85,59 +71,6 @@ class LXDTestDriver(test.NoDBTestCase): self.assertTrue( self.connection.capabilities['supports_attach_interface']) - @stubs.annotated_data( - ('running', {'state': 200, 'mem': 0, 'max_mem': 0}, - power_state.RUNNING), - ('shutdown', {'state': 102, 'mem': 0, 'max_mem': 0}, - power_state.SHUTDOWN), - ('crashed', {'state': 108, 'mem': 0, 'max_mem': 0}, - power_state.CRASHED), - ('suspend', {'state': 109, 'mem': 0, 'max_mem': 0}, - power_state.SUSPENDED), - ('no_state', {'state': 401, 'mem': 0, 'max_mem': 0}, - power_state.NOSTATE), - ) - def test_get_info(self, tag, side_effect, expected): - instance = stubs._fake_instance() - with mock.patch.object(session.LXDAPISession, - "container_state", - ) as state: - state.return_value = side_effect - info = self.connection.get_info(instance) - self.assertEqual(dir(hardware.InstanceInfo(state=expected, - num_cpu=2)), dir(info)) - - @stubs.annotated_data( - (True, 'mock-instance-1'), - (False, 'fake-instance'), - ) - def test_instance_exists(self, expected, name): - self.assertEqual( - expected, - self.connection.instance_exists(stubs.MockInstance(name=name))) - - def test_estimate_instance_overhead(self): - self.assertEqual( - {'memory_mb': 0}, - self.connection.estimate_instance_overhead(mock.Mock())) - - def test_list_instances(self): - self.assertEqual(['mock-instance-1', 'mock-instance-2'], - self.connection.list_instances()) - - def test_list_instances_fail(self): - mock_response = mock.Mock() - mock_response.json.return_value = { - 'error': 'Fake', - } - - self.connection.client.containers.all.side_effect = ( - lxdcore_exceptions.LXDAPIException(mock_response)) - self.assertRaises( - exception.NovaException, - self.connection.list_instances - ) - @mock.patch('nova.virt.configdrive.required_by') def test_spawn(self, mock_configdrive): """Test spawn method. Ensure that the right calls diff --git a/nova/virt/lxd/driver.py b/nova/virt/lxd/driver.py index 5911e196..a4f67a77 100644 --- a/nova/virt/lxd/driver.py +++ b/nova/virt/lxd/driver.py @@ -126,7 +126,7 @@ class LXDDriver(driver.ComputeDriver): assumed to be on the same system as the compute worker running this code. This is by (current) design. - See `nova.virt.driver.ComputerDriver.init_host for more + See `nova.virt.driver.ComputeDriver.init_host` for more information. """ try: @@ -135,29 +135,37 @@ class LXDDriver(driver.ComputeDriver): msg = _('Unable to connect to LXD daemon: %s') % e raise exception.HostNotFound(msg) + def cleanup_host(self, host): + """Clean up the host. + + `nova.virt.ComputeDriver` defines this method. It is overridden + here to be explicit that there is nothing to be done, as + `init_host` does not create any resources that would need to be + cleaned up. + + See `nova.virt.driver.ComputeDriver.cleanup_host` for more + information. + """ + + def get_info(self, instance): + """Return an InstanceInfo object for the instance.""" + container = self.client.containers.get(instance.name) + state = container.state() + power_state = session.LXD_POWER_STATES[state.status_code] + mem_kb = state.memory['usage'] >> 10 + max_mem_kb = state.memory['usage_peak'] >> 10 + return hardware.InstanceInfo( + state=power_state, max_mem_kb=max_mem_kb, mem_kb=mem_kb, + num_cpu=instance.flavor.vcpus, cpu_time_ns=0) + + def list_instances(self): + """Return a list of all instance names.""" + return [c.name for c in self.client.containers.all()] + # XXX: rockstar (5 July 2016) - The methods and code below this line # have not been through the cleanup process. We know the cleanup process # is complete when there is no more code below this comment, and the # comment can be removed. - def get_info(self, instance): - LOG.debug('get_info called for instance', instance=instance) - try: - container_state = self.session.container_state(instance) - return hardware.InstanceInfo(state=container_state['state'], - max_mem_kb=container_state['max_mem'], - mem_kb=container_state['mem'], - num_cpu=instance.flavor.vcpus, - cpu_time_ns=0) - except Exception as ex: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('Failed to get container info' - ' for %(instance)s: %(ex)s'), - {'instance': instance.name, 'ex': ex}, - instance=instance) - - def instance_exists(self, instance): - return instance.name in self.list_instances() - def plug_vifs(self, instance, network_info): """Plug VIFs into networks.""" for vif in network_info: @@ -175,21 +183,6 @@ class LXDDriver(driver.ComputeDriver): pass self.firewall_driver.unfilter_instance(instance, network_info) - def estimate_instance_overhead(self, instance_info): - return {'memory_mb': 0} - - def list_instances(self): - try: - return [c.name for c in self.client.containers.all()] - except lxd_exceptions.LXDAPIException as ex: - msg = _('Failed to communicate with LXD API: %(reason)s') \ - % {'reason': ex} - LOG.error(msg) - raise exception.NovaException(msg) - - def list_instance_uuids(self): - raise NotImplementedError() - def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): msg = ('Spawning container '