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:
parent
e880aa6cb3
commit
0774e313dd
@ -12,13 +12,22 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import collections
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
from nova import context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import test
|
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 pylxd import exceptions as lxdcore_exceptions
|
||||||
|
|
||||||
from nova.virt.lxd import driver
|
from nova.virt.lxd import driver
|
||||||
|
|
||||||
|
MockContainer = collections.namedtuple('Container', ['name'])
|
||||||
|
MockContainerState = collections.namedtuple(
|
||||||
|
'ContainerState', ['status_code', 'memory'])
|
||||||
|
|
||||||
|
|
||||||
class LXDDriverTest(test.NoDBTestCase):
|
class LXDDriverTest(test.NoDBTestCase):
|
||||||
"""Tests for nova.virt.lxd.driver.LXDDriver."""
|
"""Tests for nova.virt.lxd.driver.LXDDriver."""
|
||||||
@ -52,8 +61,35 @@ class LXDDriverTest(test.NoDBTestCase):
|
|||||||
|
|
||||||
lxd_driver = driver.LXDDriver(None)
|
lxd_driver = driver.LXDDriver(None)
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(exception.HostNotFound, lxd_driver.init_host, None)
|
||||||
exception.HostNotFound,
|
|
||||||
lxd_driver.init_host,
|
def test_get_info(self):
|
||||||
None
|
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)
|
||||||
|
@ -12,12 +12,10 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
import collections
|
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
from pylxd import exceptions as lxdcore_exceptions
|
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
@ -27,20 +25,15 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
from nova.compute import arch
|
from nova.compute import arch
|
||||||
from nova.compute import hv_type
|
from nova.compute import hv_type
|
||||||
from nova.compute import power_state
|
|
||||||
from nova.compute import vm_mode
|
from nova.compute import vm_mode
|
||||||
from nova import exception
|
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.virt import fake
|
from nova.virt import fake
|
||||||
from nova.virt import hardware
|
|
||||||
|
|
||||||
from nova.virt.lxd import driver
|
from nova.virt.lxd import driver
|
||||||
from nova.virt.lxd import session
|
from nova.virt.lxd import session
|
||||||
from nova.virt.lxd import utils as container_dir
|
from nova.virt.lxd import utils as container_dir
|
||||||
import stubs
|
import stubs
|
||||||
|
|
||||||
MockContainer = collections.namedtuple('Container', ['name'])
|
|
||||||
|
|
||||||
|
|
||||||
class LXDTestConfig(test.NoDBTestCase):
|
class LXDTestConfig(test.NoDBTestCase):
|
||||||
|
|
||||||
@ -70,13 +63,6 @@ class LXDTestDriver(test.NoDBTestCase):
|
|||||||
self.driver = driver.LXDDriver(mock.MagicMock())
|
self.driver = driver.LXDDriver(mock.MagicMock())
|
||||||
self.driver.container_migrate = 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):
|
def test_capabilities(self):
|
||||||
self.assertFalse(self.connection.capabilities['has_imagecache'])
|
self.assertFalse(self.connection.capabilities['has_imagecache'])
|
||||||
self.assertFalse(self.connection.capabilities['supports_recreate'])
|
self.assertFalse(self.connection.capabilities['supports_recreate'])
|
||||||
@ -85,59 +71,6 @@ class LXDTestDriver(test.NoDBTestCase):
|
|||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.connection.capabilities['supports_attach_interface'])
|
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')
|
@mock.patch('nova.virt.configdrive.required_by')
|
||||||
def test_spawn(self, mock_configdrive):
|
def test_spawn(self, mock_configdrive):
|
||||||
"""Test spawn method. Ensure that the right calls
|
"""Test spawn method. Ensure that the right calls
|
||||||
|
@ -126,7 +126,7 @@ class LXDDriver(driver.ComputeDriver):
|
|||||||
assumed to be on the same system as the compute worker
|
assumed to be on the same system as the compute worker
|
||||||
running this code. This is by (current) design.
|
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.
|
information.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -135,29 +135,37 @@ class LXDDriver(driver.ComputeDriver):
|
|||||||
msg = _('Unable to connect to LXD daemon: %s') % e
|
msg = _('Unable to connect to LXD daemon: %s') % e
|
||||||
raise exception.HostNotFound(msg)
|
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
|
# XXX: rockstar (5 July 2016) - The methods and code below this line
|
||||||
# have not been through the cleanup process. We know the cleanup process
|
# 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
|
# is complete when there is no more code below this comment, and the
|
||||||
# comment can be removed.
|
# 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):
|
def plug_vifs(self, instance, network_info):
|
||||||
"""Plug VIFs into networks."""
|
"""Plug VIFs into networks."""
|
||||||
for vif in network_info:
|
for vif in network_info:
|
||||||
@ -175,21 +183,6 @@ class LXDDriver(driver.ComputeDriver):
|
|||||||
pass
|
pass
|
||||||
self.firewall_driver.unfilter_instance(instance, network_info)
|
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,
|
def spawn(self, context, instance, image_meta, injected_files,
|
||||||
admin_password, network_info=None, block_device_info=None):
|
admin_password, network_info=None, block_device_info=None):
|
||||||
msg = ('Spawning container '
|
msg = ('Spawning container '
|
||||||
|
Loading…
Reference in New Issue
Block a user