commit
660a563e42
|
@ -1,7 +1,7 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = nova-compute-lxd
|
||||
omit = nova-compute-lxd/tests/*,nova-compute-lxd/openstack/*
|
||||
source = nclxd
|
||||
|
||||
[report]
|
||||
ignore-errors = True
|
||||
ignore-errors = True
|
||||
omit = nclxd/tests/*
|
||||
|
|
|
@ -62,15 +62,10 @@ class LXDContainerImage(object):
|
|||
|
||||
''' Upload the image to LXD '''
|
||||
with fileutils.remove_path_on_error(container_image):
|
||||
try:
|
||||
self.lxd.image_defined(instance.image_ref)
|
||||
except lxd_exceptions.APIError as e:
|
||||
if e.status_code == 404:
|
||||
pass
|
||||
else:
|
||||
raise exception.ImageUnacceptable(
|
||||
image_id=instance.image_ref,
|
||||
reason=_('Image already exists.'))
|
||||
if self.lxd.image_defined(instance.image_ref):
|
||||
raise exception.ImageUnacceptable(
|
||||
image_id=instance.image_ref,
|
||||
reason=_('Image already exists.'))
|
||||
|
||||
try:
|
||||
LOG.debug('Uploading image: %s' % container_image)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright (c) 2015 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
|
||||
class MockConf(mock.Mock):
|
||||
|
||||
def __init__(self, lxd_args=(), lxd_kwargs={}, *args, **kwargs):
|
||||
default = {
|
||||
'config_drive_format': None,
|
||||
'instances_path': '/fake/instances/path',
|
||||
'image_cache_subdirectory_name': '/fake/image/cache',
|
||||
}
|
||||
default.update(kwargs)
|
||||
super(MockConf, self).__init__(*args, **default)
|
||||
|
||||
lxd_default = {
|
||||
'lxd_default_profile': 'fake_profile',
|
||||
'lxd_root_dir': '/fake/lxd/root',
|
||||
}
|
||||
lxd_default.update(lxd_kwargs)
|
||||
self.lxd = mock.Mock(lxd_args, **lxd_default)
|
||||
|
||||
|
||||
class MockInstance(mock.Mock):
|
||||
|
||||
def __init__(self, name='mock_instance', image_ref='mock_image',
|
||||
memory_mb=-1, vcpus=0, *args, **kwargs):
|
||||
super(MockInstance, self).__init__(
|
||||
*args, **kwargs)
|
||||
self.name = name
|
||||
self.image_ref = image_ref
|
||||
self.flavor = mock.Mock(memory_mb=memory_mb, vcpus=vcpus)
|
||||
|
||||
|
||||
def annotated_data(*args):
|
||||
class List(list):
|
||||
pass
|
||||
|
||||
new_args = []
|
||||
|
||||
for arg in args:
|
||||
new_arg = List(arg)
|
||||
new_arg.__name__ = arg[0]
|
||||
new_args.append(new_arg)
|
||||
|
||||
return lambda func: ddt.data(*new_args)(ddt.unpack(func))
|
|
@ -13,16 +13,167 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from oslo_config import cfg
|
||||
|
||||
from nclxd.nova.virt.lxd import container_config
|
||||
|
||||
CONF = cfg.CONF
|
||||
from nclxd.nova.virt.lxd import container_utils
|
||||
from nclxd import tests
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.object(container_config, 'CONF', tests.MockConf())
|
||||
@mock.patch.object(container_utils, 'CONF', tests.MockConf())
|
||||
class LXDTestContainerConfig(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LXDTestContainerConfig, self).setUp()
|
||||
self.container_config = container_config.LXDContainerConfig()
|
||||
|
||||
def test_init_config(self):
|
||||
self.assertEqual({'config': {}, 'devices': {}},
|
||||
self.container_config._init_container_config())
|
||||
|
||||
@mock.patch('nclxd.nova.virt.lxd.container_image'
|
||||
'.LXDContainerImage.fetch_image')
|
||||
@tests.annotated_data(
|
||||
('no_rescue', {}, 'mock_instance'),
|
||||
('rescue', {'name_label': 'rescued', 'rescue': True}, 'rescued'),
|
||||
)
|
||||
def test_configure_container(self, tag, kwargs, expected, mf):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
network_info = []
|
||||
image_meta = {}
|
||||
self.assertEqual(
|
||||
{'config': {'raw.lxc':
|
||||
'lxc.console.logfile=/fake/lxd/root/lxc/'
|
||||
'mock_instance/console.log\n'},
|
||||
'devices': {},
|
||||
'name': expected,
|
||||
'profiles': ['fake_profile'],
|
||||
'source': {'alias': 'None', 'type': 'image'}},
|
||||
(self.container_config
|
||||
.configure_container(context,
|
||||
instance,
|
||||
network_info,
|
||||
image_meta,
|
||||
**kwargs)))
|
||||
mf.assert_called_once_with(context, instance, image_meta)
|
||||
|
||||
@tests.annotated_data(
|
||||
('no_limits', {'memory_mb': -1, 'vcpus': 0},
|
||||
{}),
|
||||
('mem_limit', {'memory_mb': 2048, 'vcpus': 0},
|
||||
{'limits.memory': '2147483648'}),
|
||||
('cpu_limit', {'memory_mb': -1, 'vcpus': 10},
|
||||
{'limits.cpus': '10'}),
|
||||
('both_limits', {'memory_mb': 4096, 'vcpus': 20},
|
||||
{'limits.memory': '4294967296', 'limits.cpus': '20'}),
|
||||
)
|
||||
def test_configure_container_config(self, tag, flavor, expected):
|
||||
instance = tests.MockInstance(**flavor)
|
||||
config = {'raw.lxc': 'lxc.console.logfile=/fake/lxd/root/lxc/'
|
||||
'mock_instance/console.log\n'}
|
||||
config.update(expected)
|
||||
self.assertEqual(
|
||||
{'config': config},
|
||||
self.container_config.configure_container_config({},
|
||||
instance))
|
||||
|
||||
def test_configure_network_devices(self):
|
||||
instance = tests.MockInstance()
|
||||
network_info = (
|
||||
{
|
||||
'id': '0123456789abcdef',
|
||||
'address': '00:11:22:33:44:55',
|
||||
},
|
||||
{
|
||||
'id': 'fedcba9876543210',
|
||||
'address': '66:77:88:99:aa:bb',
|
||||
})
|
||||
|
||||
self.assertEqual({
|
||||
'devices': {
|
||||
'qbr0123456789a': {
|
||||
'nictype': 'bridged',
|
||||
'hwaddr': '00:11:22:33:44:55',
|
||||
'parent': 'qbr0123456789a',
|
||||
'type': 'nic'
|
||||
},
|
||||
'qbrfedcba98765': {
|
||||
'nictype': 'bridged',
|
||||
'hwaddr': '66:77:88:99:aa:bb',
|
||||
'parent': 'qbrfedcba98765',
|
||||
'type': 'nic'
|
||||
}}},
|
||||
self.container_config.configure_network_devices(
|
||||
{}, instance, network_info))
|
||||
|
||||
def test_configure_container_rescuedisk(self):
|
||||
instance = tests.MockInstance()
|
||||
self.assertEqual({
|
||||
'devices':
|
||||
{'rescue': {'path': 'mnt',
|
||||
'source': '/fake/lxd/root/lxc/mock_instance/rootfs',
|
||||
'type': 'disk'}}},
|
||||
self.container_config.configure_container_rescuedisk(
|
||||
{}, instance))
|
||||
|
||||
def test_configure_container_configdrive_wrong_format(self):
|
||||
instance = tests.MockInstance()
|
||||
with mock.patch.object(container_config.CONF, 'config_drive_format',
|
||||
new='fake-format'):
|
||||
self.assertRaises(
|
||||
exception.InstancePowerOnFailure,
|
||||
self.container_config.configure_container_configdrive,
|
||||
{}, instance, {}, 'secret')
|
||||
|
||||
@mock.patch('nova.api.metadata.base.InstanceMetadata')
|
||||
def test_configure_container_configdrive_fail(self, mi):
|
||||
instance = None
|
||||
injected_files = mock.Mock()
|
||||
self.assertRaises(
|
||||
AttributeError,
|
||||
self.container_config.configure_container_configdrive,
|
||||
{}, instance, injected_files, 'secret')
|
||||
mi.assert_called_once_with(
|
||||
instance, content=injected_files, extra_md={})
|
||||
|
||||
@mock.patch('nova.api.metadata.base.InstanceMetadata')
|
||||
@mock.patch('nova.virt.configdrive.ConfigDriveBuilder')
|
||||
def test_configure_container_configdrive_fail_dir(self, md, mi):
|
||||
instance = tests.MockInstance()
|
||||
injected_files = mock.Mock()
|
||||
self.assertRaises(
|
||||
AttributeError,
|
||||
self.container_config.configure_container_configdrive,
|
||||
None, instance, injected_files, 'secret')
|
||||
md.assert_called_once_with(instance_md=mi.return_value)
|
||||
(md.return_value.__enter__.return_value
|
||||
.make_drive.assert_called_once_with(
|
||||
'/fake/instances/path/mock_instance/config-drive'))
|
||||
mi.assert_called_once_with(
|
||||
instance, content=injected_files, extra_md={})
|
||||
|
||||
@mock.patch('nova.api.metadata.base.InstanceMetadata')
|
||||
@mock.patch('nova.virt.configdrive.ConfigDriveBuilder')
|
||||
def test_configure_container_configdrive(self, md, mi):
|
||||
instance = tests.MockInstance()
|
||||
injected_files = mock.Mock()
|
||||
self.assertEqual(
|
||||
{'devices': {'configdrive':
|
||||
{'path': 'mnt',
|
||||
'type': 'disk',
|
||||
'source': '/fake/instances/path/'
|
||||
'mock_instance/config-drive'}}},
|
||||
self.container_config.configure_container_configdrive(
|
||||
{}, instance, injected_files, 'secret'))
|
||||
md.assert_called_once_with(instance_md=mi.return_value)
|
||||
(md.return_value.__enter__.return_value
|
||||
.make_drive.assert_called_once_with(
|
||||
'/fake/instances/path/mock_instance/config-drive'))
|
||||
mi.assert_called_once_with(
|
||||
instance, content=injected_files, extra_md={})
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# Copyright 2015 Canonical Ltd
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 ddt
|
||||
import mock
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from pylxd import exceptions as lxd_exceptions
|
||||
|
||||
from nclxd.nova.virt.lxd import container_image
|
||||
from nclxd.nova.virt.lxd import container_utils
|
||||
from nclxd import tests
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@mock.patch.object(container_image, 'CONF', tests.MockConf())
|
||||
@mock.patch.object(container_utils, 'CONF', tests.MockConf())
|
||||
class LXDTestContainerImage(test.NoDBTestCase):
|
||||
|
||||
@mock.patch.object(container_utils, 'CONF', tests.MockConf())
|
||||
def setUp(self):
|
||||
super(LXDTestContainerImage, self).setUp()
|
||||
self.container_image = container_image.LXDContainerImage()
|
||||
alias_patcher = mock.patch.object(self.container_image.lxd,
|
||||
'alias_list',
|
||||
return_value=['alias'])
|
||||
alias_patcher.start()
|
||||
self.addCleanup(alias_patcher.stop)
|
||||
|
||||
def test_fetch_image_existing_alias(self):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'alias'}
|
||||
self.assertEqual(None,
|
||||
self.container_image.fetch_image(context,
|
||||
instance,
|
||||
image_meta))
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('nova.openstack.common.fileutils.ensure_tree')
|
||||
@ddt.data(True, False)
|
||||
def test_fetch_image_existing_file(self, base_exists, mt, mo):
|
||||
mo.side_effect = [base_exists, True]
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'new_image'}
|
||||
self.assertEqual(None,
|
||||
self.container_image.fetch_image(context,
|
||||
instance,
|
||||
image_meta))
|
||||
if base_exists:
|
||||
self.assertFalse(mt.called)
|
||||
else:
|
||||
mt.assert_called_once_with('/fake/image/cache')
|
||||
self.assertEqual([mock.call('/fake/image/cache'),
|
||||
mock.call('/fake/image/cache/new_image.tar.gz')],
|
||||
mo.call_args_list)
|
||||
|
||||
@mock.patch('os.path.exists', mock.Mock(return_value=False))
|
||||
@mock.patch('nova.openstack.common.fileutils.ensure_tree', mock.Mock())
|
||||
@mock.patch('nova.openstack.common.fileutils.remove_path_on_error')
|
||||
def test_fetch_image_new_defined(self, mf):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'new_image'}
|
||||
with (
|
||||
mock.patch.object(container_image.IMAGE_API,
|
||||
'download')) as mi, (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_defined', return_value=True)) as ml:
|
||||
self.assertRaises(exception.ImageUnacceptable,
|
||||
self.container_image.fetch_image,
|
||||
context, instance, image_meta)
|
||||
ml.assert_called_once_with('mock_image')
|
||||
mf.assert_called_once_with('/fake/image/cache/new_image.tar.gz')
|
||||
mi.assert_called_once_with(
|
||||
context, 'mock_image',
|
||||
dest_path='/fake/image/cache/new_image.tar.gz')
|
||||
|
||||
@mock.patch('os.path.exists', mock.Mock(return_value=False))
|
||||
@mock.patch('nova.openstack.common.fileutils.ensure_tree', mock.Mock())
|
||||
@mock.patch('nova.openstack.common.fileutils.remove_path_on_error',
|
||||
mock.MagicMock())
|
||||
def test_fetch_image_new_upload_failed(self):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'new_image'}
|
||||
with (
|
||||
mock.patch.object(container_image.IMAGE_API,
|
||||
'download')), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_defined', return_value=False)), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_upload',
|
||||
side_effect=lxd_exceptions.APIError(
|
||||
'Fake error', 500))) as mu:
|
||||
self.assertRaises(exception.ImageUnacceptable,
|
||||
self.container_image.fetch_image,
|
||||
context, instance, image_meta)
|
||||
mu.assert_called_once_with(
|
||||
path='/fake/image/cache/new_image.tar.gz')
|
||||
|
||||
@mock.patch('os.path.exists', mock.Mock(return_value=False))
|
||||
@mock.patch('nova.openstack.common.fileutils.ensure_tree', mock.Mock())
|
||||
@mock.patch('nova.openstack.common.fileutils.remove_path_on_error',
|
||||
mock.MagicMock())
|
||||
def test_fetch_image_new_alias_failed(self):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'new_image'}
|
||||
with (
|
||||
mock.patch.object(container_image.IMAGE_API,
|
||||
'download')), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_defined', return_value=False)), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_upload')), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'alias_create',
|
||||
side_effect=lxd_exceptions.APIError(
|
||||
'Fake error', 500))), (
|
||||
mock.patch('six.moves.builtins.open')) as mo:
|
||||
mo.return_value.__enter__.return_value.read.return_value = b'image'
|
||||
self.assertRaises(exception.ImageUnacceptable,
|
||||
self.container_image.fetch_image,
|
||||
context, instance, image_meta)
|
||||
|
||||
@mock.patch('os.path.exists', mock.Mock(return_value=False))
|
||||
@mock.patch('nova.openstack.common.fileutils.ensure_tree', mock.Mock())
|
||||
@mock.patch('nova.openstack.common.fileutils.remove_path_on_error',
|
||||
mock.MagicMock())
|
||||
def test_fetch_image_new(self):
|
||||
instance = tests.MockInstance()
|
||||
context = {}
|
||||
image_meta = {'name': 'new_image'}
|
||||
with (
|
||||
mock.patch.object(container_image.IMAGE_API,
|
||||
'download')), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_defined', return_value=False)), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'image_upload')), (
|
||||
mock.patch.object(self.container_image.lxd,
|
||||
'alias_create')) as ma, (
|
||||
mock.patch('six.moves.builtins.open')) as mo:
|
||||
mo.return_value.__enter__.return_value.read.return_value = b'image'
|
||||
self.assertEqual(None,
|
||||
self.container_image.fetch_image(context,
|
||||
instance,
|
||||
image_meta))
|
||||
ma.assert_called_with(
|
||||
{'name': 'new_image',
|
||||
'target': '6105d6cc76af400325e94d588ce511be'
|
||||
'5bfdbb73b437dc51eca43917d7a43e3d'})
|
|
@ -6,6 +6,7 @@ hacking<0.10,>=0.9.2
|
|||
|
||||
coverage>=3.6
|
||||
discover
|
||||
ddt
|
||||
python-subunit>=0.0.18
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue