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
This commit is contained in:
Paul Hummer 2016-07-05 22:13:34 -06:00
parent e880aa6cb3
commit 0774e313dd
3 changed files with 69 additions and 107 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 '