5e7b840e48
Validate the combination of the flavor extra-specs and image properties as early as possible once they're both known (since you can specify mutually-incompatible changes in the two places). If validation failed then synchronously return error to user. We need to do this anywhere the flavor or image changes, so basically instance creation, rebuild, and resize. - Rename _check_requested_image() to _validate_flavor_image() and add a call from the resize code path. (It's already called for create and rebuild.) - In _validate_flavor_image() add new checks to validate numa related options from flavor and image including CPU policy, CPU thread policy, CPU topology, memory topology, hugepages, CPU pinning, serial ports, realtime mask, etc. - Added new 400 exceptions in Server API correspondent to added validations. blueprint: flavor-extra-spec-image-property-validation Change-Id: I06fad233006c7bab14749a51ffa226c3801f951b Signed-off-by: Jack Ding <jack.ding@windriver.com> Signed-off-by: Chris Friesen <chris.friesen@windriver.com>
736 lines
32 KiB
Python
736 lines
32 KiB
Python
# Copyright (c) 2012 Rackspace Hosting
|
|
# 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.
|
|
"""
|
|
Tests For Compute w/ Cells
|
|
"""
|
|
import copy
|
|
import functools
|
|
import inspect
|
|
|
|
import mock
|
|
from oslo_utils.fixture import uuidsentinel as uuids
|
|
from oslo_utils import timeutils
|
|
|
|
from nova import block_device
|
|
from nova.cells import manager
|
|
from nova.compute import api as compute_api
|
|
from nova.compute import cells_api as compute_cells_api
|
|
from nova.compute import flavors
|
|
from nova.compute import power_state
|
|
from nova.compute import utils as compute_utils
|
|
from nova.compute import vm_states
|
|
from nova import context
|
|
from nova.db import api as db
|
|
from nova.db.sqlalchemy import api as db_api
|
|
from nova.db.sqlalchemy import api_models
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.objects import fields as obj_fields
|
|
from nova import quota
|
|
from nova import test
|
|
from nova.tests.unit.compute import test_compute
|
|
from nova.tests.unit.compute import test_shelve
|
|
from nova.tests.unit import fake_instance
|
|
from nova.tests.unit.objects import test_flavor
|
|
|
|
|
|
ORIG_COMPUTE_API = None
|
|
FAKE_IMAGE_REF = uuids.image_ref
|
|
|
|
NODENAME = 'fakenode1'
|
|
NODENAME2 = 'fakenode2'
|
|
|
|
|
|
def stub_call_to_cells(context, instance, method, *args, **kwargs):
|
|
fn = getattr(ORIG_COMPUTE_API, method)
|
|
original_instance = kwargs.pop('original_instance', None)
|
|
if original_instance:
|
|
instance = original_instance
|
|
# Restore this in 'child cell DB'
|
|
db.instance_update(context, instance['uuid'],
|
|
dict(vm_state=instance['vm_state'],
|
|
task_state=instance['task_state']))
|
|
|
|
# Use NoopQuotaDriver in child cells.
|
|
saved_quotas = quota.QUOTAS
|
|
quota.QUOTAS = quota.QuotaEngine(
|
|
quota_driver=quota.NoopQuotaDriver())
|
|
compute_api.QUOTAS = quota.QUOTAS
|
|
try:
|
|
return fn(context, instance, *args, **kwargs)
|
|
finally:
|
|
quota.QUOTAS = saved_quotas
|
|
compute_api.QUOTAS = saved_quotas
|
|
|
|
|
|
def stub_cast_to_cells(context, instance, method, *args, **kwargs):
|
|
fn = getattr(ORIG_COMPUTE_API, method)
|
|
original_instance = kwargs.pop('original_instance', None)
|
|
if original_instance:
|
|
instance = original_instance
|
|
# Restore this in 'child cell DB'
|
|
db.instance_update(context, instance['uuid'],
|
|
dict(vm_state=instance['vm_state'],
|
|
task_state=instance['task_state']))
|
|
|
|
# Use NoopQuotaDriver in child cells.
|
|
saved_quotas = quota.QUOTAS
|
|
quota.QUOTAS = quota.QuotaEngine(
|
|
quota_driver=quota.NoopQuotaDriver())
|
|
compute_api.QUOTAS = quota.QUOTAS
|
|
try:
|
|
fn(context, instance, *args, **kwargs)
|
|
finally:
|
|
quota.QUOTAS = saved_quotas
|
|
compute_api.QUOTAS = saved_quotas
|
|
|
|
|
|
def deploy_stubs(stubs, api, original_instance=None):
|
|
call = stub_call_to_cells
|
|
cast = stub_cast_to_cells
|
|
|
|
if original_instance:
|
|
kwargs = dict(original_instance=original_instance)
|
|
call = functools.partial(stub_call_to_cells, **kwargs)
|
|
cast = functools.partial(stub_cast_to_cells, **kwargs)
|
|
|
|
stubs.Set(api, '_call_to_cells', call)
|
|
stubs.Set(api, '_cast_to_cells', cast)
|
|
|
|
|
|
class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
|
|
def setUp(self):
|
|
self.flags(use_neutron=False)
|
|
super(CellsComputeAPITestCase, self).setUp()
|
|
global ORIG_COMPUTE_API
|
|
ORIG_COMPUTE_API = self.compute_api
|
|
self.flags(enable=True, group='cells')
|
|
|
|
def _fake_validate_cell(*args, **kwargs):
|
|
return
|
|
|
|
self.compute_api = compute_cells_api.ComputeCellsAPI()
|
|
self.stubs.Set(self.compute_api, '_validate_cell',
|
|
_fake_validate_cell)
|
|
|
|
deploy_stubs(self.stubs, self.compute_api)
|
|
|
|
def tearDown(self):
|
|
global ORIG_COMPUTE_API
|
|
self.compute_api = ORIG_COMPUTE_API
|
|
super(CellsComputeAPITestCase, self).tearDown()
|
|
|
|
def test_instance_metadata(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
def _test_evacuate(self, force=None):
|
|
@mock.patch.object(compute_api.API, 'evacuate')
|
|
def _test(mock_evacuate):
|
|
instance = objects.Instance(uuid=uuids.evacuate_instance,
|
|
cell_name='fake_cell_name')
|
|
dest_host = 'fake_cell_name@fakenode2'
|
|
self.compute_api.evacuate(self.context, instance, host=dest_host,
|
|
force=force)
|
|
mock_evacuate.assert_called_once_with(
|
|
self.context, instance, 'fakenode2', force=force)
|
|
|
|
_test()
|
|
|
|
def test_error_evacuate(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
def test_create_instance_sets_system_metadata(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
def test_create_saves_flavor(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
def test_create_instance_associates_security_groups(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
@mock.patch('nova.objects.quotas.Quotas.check_deltas')
|
|
def test_create_instance_over_quota_during_recheck(
|
|
self, check_deltas_mock):
|
|
self.stub_out('nova.tests.unit.image.fake._FakeImageService.show',
|
|
self.fake_show)
|
|
|
|
# Simulate a race where the first check passes and the recheck fails.
|
|
fake_quotas = {'instances': 5, 'cores': 10, 'ram': 4096}
|
|
fake_headroom = {'instances': 5, 'cores': 10, 'ram': 4096}
|
|
fake_usages = {'instances': 5, 'cores': 10, 'ram': 4096}
|
|
exc = exception.OverQuota(overs=['instances'], quotas=fake_quotas,
|
|
headroom=fake_headroom, usages=fake_usages)
|
|
check_deltas_mock.side_effect = [None, exc]
|
|
|
|
inst_type = flavors.get_default_flavor()
|
|
# Try to create 3 instances.
|
|
self.assertRaises(exception.QuotaError, self.compute_api.create,
|
|
self.context, inst_type, self.fake_image['id'], min_count=3)
|
|
|
|
project_id = self.context.project_id
|
|
|
|
self.assertEqual(2, check_deltas_mock.call_count)
|
|
call1 = mock.call(self.context,
|
|
{'instances': 3, 'cores': inst_type.vcpus * 3,
|
|
'ram': inst_type.memory_mb * 3},
|
|
project_id, user_id=None,
|
|
check_project_id=project_id, check_user_id=None)
|
|
call2 = mock.call(self.context, {'instances': 0, 'cores': 0, 'ram': 0},
|
|
project_id, user_id=None,
|
|
check_project_id=project_id, check_user_id=None)
|
|
check_deltas_mock.assert_has_calls([call1, call2])
|
|
|
|
# Verify we removed the artifacts that were added after the first
|
|
# quota check passed.
|
|
instances = objects.InstanceList.get_all(self.context)
|
|
self.assertEqual(0, len(instances))
|
|
build_requests = objects.BuildRequestList.get_all(self.context)
|
|
self.assertEqual(0, len(build_requests))
|
|
|
|
@db_api.api_context_manager.reader
|
|
def request_spec_get_all(context):
|
|
return context.session.query(api_models.RequestSpec).all()
|
|
|
|
request_specs = request_spec_get_all(self.context)
|
|
self.assertEqual(0, len(request_specs))
|
|
|
|
instance_mappings = objects.InstanceMappingList.get_by_project_id(
|
|
self.context, project_id)
|
|
self.assertEqual(0, len(instance_mappings))
|
|
|
|
@mock.patch('nova.objects.quotas.Quotas.check_deltas')
|
|
def test_create_instance_no_quota_recheck(
|
|
self, check_deltas_mock):
|
|
self.stub_out('nova.tests.unit.image.fake._FakeImageService.show',
|
|
self.fake_show)
|
|
# Disable recheck_quota.
|
|
self.flags(recheck_quota=False, group='quota')
|
|
|
|
inst_type = flavors.get_default_flavor()
|
|
(refs, resv_id) = self.compute_api.create(self.context,
|
|
inst_type,
|
|
self.fake_image['id'])
|
|
self.assertEqual(1, len(refs))
|
|
|
|
project_id = self.context.project_id
|
|
|
|
# check_deltas should have been called only once.
|
|
check_deltas_mock.assert_called_once_with(self.context,
|
|
{'instances': 1,
|
|
'cores': inst_type.vcpus,
|
|
'ram': inst_type.memory_mb},
|
|
project_id, user_id=None,
|
|
check_project_id=project_id,
|
|
check_user_id=None)
|
|
|
|
@mock.patch.object(compute_api.API, '_local_delete')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, None))
|
|
def test_delete_instance_no_cell_instance_disappear(self, mock_lookup,
|
|
mock_local_delete):
|
|
inst = self._create_fake_instance_obj()
|
|
|
|
@mock.patch.object(self.compute_api.cells_rpcapi,
|
|
'instance_delete_everywhere')
|
|
def test(mock_inst_del):
|
|
self.compute_api.delete(self.context, inst)
|
|
mock_lookup.assert_called_once_with(self.context, inst.uuid)
|
|
mock_inst_del.assert_called_once_with(self.context, inst, 'hard')
|
|
self.assertFalse(mock_local_delete.called)
|
|
|
|
test()
|
|
|
|
@mock.patch.object(compute_api.API, '_local_delete')
|
|
def _test_delete_instance_no_cell(self, method_name, mock_local_delete):
|
|
cells_rpcapi = self.compute_api.cells_rpcapi
|
|
inst = self._create_fake_instance_obj()
|
|
delete_type = method_name == 'soft_delete' and 'soft' or 'hard'
|
|
|
|
@mock.patch.object(cells_rpcapi,
|
|
'instance_delete_everywhere')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, inst))
|
|
def test(mock_lookup, mock_inst_del):
|
|
self.stub_out('nova.network.api.deallocate_for_instance',
|
|
lambda *a, **kw: None)
|
|
getattr(self.compute_api, method_name)(self.context, inst)
|
|
mock_lookup.assert_called_once_with(self.context, inst.uuid)
|
|
mock_local_delete.assert_called_once_with(self.context, inst,
|
|
mock.ANY, method_name,
|
|
mock.ANY)
|
|
mock_inst_del.assert_called_once_with(self.context,
|
|
inst, delete_type)
|
|
|
|
test()
|
|
|
|
def test_delete_instance_no_cell_constraint_failure_does_not_loop(self):
|
|
inst = self._create_fake_instance_obj()
|
|
inst.cell_name = None
|
|
|
|
inst.destroy = mock.MagicMock()
|
|
inst.destroy.side_effect = exception.ObjectActionError(action='',
|
|
reason='')
|
|
inst.refresh = mock.MagicMock()
|
|
|
|
@mock.patch.object(self.compute_api.cells_rpcapi,
|
|
'instance_delete_everywhere')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, inst))
|
|
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
|
|
def _test(mock_get_im, _mock_lookup_inst, _mock_delete_everywhere):
|
|
self.assertRaises(exception.ObjectActionError,
|
|
self.compute_api.delete, self.context, inst)
|
|
inst.destroy.assert_called_once_with()
|
|
mock_get_im.assert_called_once_with(self.context, inst.uuid)
|
|
|
|
_test()
|
|
|
|
def test_delete_instance_no_cell_constraint_failure_corrects_itself(self):
|
|
|
|
def add_cell_name(context, instance, delete_type):
|
|
instance.cell_name = 'fake_cell_name'
|
|
|
|
inst = self._create_fake_instance_obj()
|
|
inst.cell_name = None
|
|
|
|
inst.destroy = mock.MagicMock()
|
|
inst.destroy.side_effect = exception.ObjectActionError(action='',
|
|
reason='')
|
|
inst.refresh = mock.MagicMock()
|
|
|
|
@mock.patch.object(compute_api.API, 'delete')
|
|
@mock.patch.object(self.compute_api.cells_rpcapi,
|
|
'instance_delete_everywhere', side_effect=add_cell_name)
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, inst))
|
|
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
|
|
def _test(mock_get_im, _mock_lookup_inst, mock_delete_everywhere,
|
|
mock_compute_delete):
|
|
self.compute_api.delete(self.context, inst)
|
|
inst.destroy.assert_called_once_with()
|
|
|
|
mock_compute_delete.assert_called_once_with(self.context, inst)
|
|
mock_get_im.assert_called_once_with(self.context, inst.uuid)
|
|
|
|
_test()
|
|
|
|
def test_delete_instance_no_cell_destroy_fails_already_deleted(self):
|
|
# If the instance.destroy() is reached during _local_delete,
|
|
# it will raise ObjectActionError if the instance has already
|
|
# been deleted by a instance_destroy_at_top, and instance.refresh()
|
|
# will raise InstanceNotFound
|
|
instance = objects.Instance(context=self.context,
|
|
uuid=uuids.destroy_instance,
|
|
cell_name=None, host=None)
|
|
actionerror = exception.ObjectActionError(action='destroy', reason='')
|
|
notfound = exception.InstanceNotFound(instance_id=instance.uuid)
|
|
|
|
@mock.patch.object(compute_api.API, 'delete')
|
|
@mock.patch.object(self.compute_api.cells_rpcapi,
|
|
'instance_delete_everywhere')
|
|
@mock.patch.object(compute_api.API, '_local_delete',
|
|
side_effect=actionerror)
|
|
@mock.patch.object(instance, 'refresh', side_effect=notfound)
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, instance))
|
|
def _test(_mock_lookup_instance, mock_refresh, mock_local_delete,
|
|
mock_delete_everywhere, mock_compute_delete):
|
|
self.compute_api.delete(self.context, instance)
|
|
mock_delete_everywhere.assert_called_once_with(self.context,
|
|
instance, 'hard')
|
|
mock_local_delete.assert_called_once_with(self.context,
|
|
instance, mock.ANY, 'delete', self.compute_api._do_delete)
|
|
mock_refresh.assert_called_once_with()
|
|
self.assertFalse(mock_compute_delete.called)
|
|
|
|
_test()
|
|
|
|
def test_delete_instance_no_cell_instance_not_found_already_deleted(self):
|
|
# If anything in _local_delete accesses the instance causing a db
|
|
# lookup before instance.destroy() is reached, if the instance has
|
|
# already been deleted by a instance_destroy_at_top,
|
|
# InstanceNotFound will be raised
|
|
instance = objects.Instance(context=self.context,
|
|
uuid=uuids.delete_instance, cell_name=None,
|
|
host=None)
|
|
notfound = exception.InstanceNotFound(instance_id=instance.uuid)
|
|
|
|
@mock.patch.object(compute_api.API, 'delete')
|
|
@mock.patch.object(self.compute_api.cells_rpcapi,
|
|
'instance_delete_everywhere')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance',
|
|
return_value=(None, instance))
|
|
@mock.patch.object(compute_api.API, '_local_delete',
|
|
side_effect=notfound)
|
|
def _test(mock_local_delete, _mock_lookup, mock_delete_everywhere,
|
|
mock_compute_delete):
|
|
self.compute_api.delete(self.context, instance)
|
|
mock_delete_everywhere.assert_called_once_with(self.context,
|
|
instance, 'hard')
|
|
mock_local_delete.assert_called_once_with(self.context,
|
|
instance, mock.ANY, 'delete', self.compute_api._do_delete)
|
|
self.assertFalse(mock_compute_delete.called)
|
|
|
|
_test()
|
|
|
|
def test_soft_delete_instance_no_cell(self):
|
|
self._test_delete_instance_no_cell('soft_delete')
|
|
|
|
def test_delete_instance_no_cell(self):
|
|
self._test_delete_instance_no_cell('delete')
|
|
|
|
def test_force_delete_instance_no_cell(self):
|
|
self._test_delete_instance_no_cell('force_delete')
|
|
|
|
@mock.patch.object(compute_api.API, '_delete_while_booting',
|
|
side_effect=exception.ObjectActionError(
|
|
action='delete', reason='host now set'))
|
|
@mock.patch.object(compute_api.API, '_local_delete')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance')
|
|
@mock.patch.object(compute_api.API, 'delete')
|
|
def test_delete_instance_no_cell_then_cell(self, mock_delete,
|
|
mock_lookup_instance,
|
|
mock_local_delete,
|
|
mock_delete_while_booting):
|
|
# This checks the case where initially an instance has no cell_name,
|
|
# and therefore no host, set but instance.destroy fails because
|
|
# there is now a host.
|
|
instance = self._create_fake_instance_obj()
|
|
instance_with_cell = copy.deepcopy(instance)
|
|
instance_with_cell.cell_name = 'foo'
|
|
mock_lookup_instance.return_value = None, instance_with_cell
|
|
|
|
cells_rpcapi = self.compute_api.cells_rpcapi
|
|
|
|
@mock.patch.object(cells_rpcapi, 'instance_delete_everywhere')
|
|
def test(mock_inst_delete_everywhere):
|
|
self.compute_api.delete(self.context, instance)
|
|
mock_local_delete.assert_not_called()
|
|
mock_delete.assert_called_once_with(self.context,
|
|
instance_with_cell)
|
|
|
|
test()
|
|
|
|
@mock.patch.object(compute_api.API, '_delete_while_booting',
|
|
side_effect=exception.ObjectActionError(
|
|
action='delete', reason='host now set'))
|
|
@mock.patch.object(compute_api.API, '_local_delete')
|
|
@mock.patch.object(compute_api.API, '_lookup_instance')
|
|
@mock.patch.object(compute_api.API, 'delete')
|
|
def test_delete_instance_no_cell_then_no_instance(self,
|
|
mock_delete, mock_lookup_instance, mock_local_delete,
|
|
mock_delete_while_booting):
|
|
# This checks the case where initially an instance has no cell_name,
|
|
# and therefore no host, set but instance.destroy fails because
|
|
# there is now a host. And then the instance can't be looked up.
|
|
instance = self._create_fake_instance_obj()
|
|
mock_lookup_instance.return_value = None, None
|
|
|
|
cells_rpcapi = self.compute_api.cells_rpcapi
|
|
|
|
@mock.patch.object(cells_rpcapi, 'instance_delete_everywhere')
|
|
def test(mock_inst_delete_everywhere):
|
|
self.compute_api.delete(self.context, instance)
|
|
mock_local_delete.assert_not_called()
|
|
mock_delete.assert_not_called()
|
|
|
|
test()
|
|
|
|
def test_get_migrations(self):
|
|
filters = {'cell_name': 'ChildCell', 'status': 'confirmed'}
|
|
migrations = {'migrations': [{'id': 1234}]}
|
|
|
|
@mock.patch.object(self.compute_api.cells_rpcapi, 'get_migrations',
|
|
return_value=migrations)
|
|
def test(mock_cell_get_migrations):
|
|
response = self.compute_api.get_migrations(self.context,
|
|
filters)
|
|
mock_cell_get_migrations.assert_called_once_with(self.context,
|
|
filters)
|
|
self.assertEqual(migrations, response)
|
|
|
|
test()
|
|
|
|
def test_create_block_device_mapping(self):
|
|
instance_type = {'swap': 1, 'ephemeral_gb': 1}
|
|
instance = self._create_fake_instance_obj()
|
|
bdms = [block_device.BlockDeviceDict({'source_type': 'image',
|
|
'destination_type': 'local',
|
|
'image_id': uuids.image,
|
|
'boot_index': 0})]
|
|
self.compute_api._create_block_device_mapping(
|
|
instance_type, instance.uuid, bdms)
|
|
bdms = db.block_device_mapping_get_all_by_instance(
|
|
self.context, instance['uuid'])
|
|
self.assertEqual(0, len(bdms))
|
|
|
|
def test_create_bdm_from_flavor(self):
|
|
self.skipTest("Test is incompatible with cells.")
|
|
|
|
@mock.patch('nova.cells.messaging._TargetedMessage')
|
|
def test_rebuild_sig(self, mock_msg):
|
|
# TODO(belliott) Cells could benefit from better testing to ensure API
|
|
# and manager signatures stay up to date
|
|
|
|
def wire(version):
|
|
# wire the rpc cast directly to the manager method to make sure
|
|
# the signature matches
|
|
cells_mgr = manager.CellsManager()
|
|
|
|
def cast(context, method, *args, **kwargs):
|
|
fn = getattr(cells_mgr, method)
|
|
fn(context, *args, **kwargs)
|
|
|
|
cells_mgr.cast = cast
|
|
return cells_mgr
|
|
|
|
cells_rpcapi = self.compute_api.cells_rpcapi
|
|
client = cells_rpcapi.client
|
|
|
|
with mock.patch.object(client, 'prepare', side_effect=wire):
|
|
inst = self._create_fake_instance_obj()
|
|
inst.cell_name = 'mycell'
|
|
|
|
cells_rpcapi.rebuild_instance(self.context, inst, 'pass', None,
|
|
None, None, None, None,
|
|
recreate=False,
|
|
on_shared_storage=False, host='host',
|
|
preserve_ephemeral=True, kwargs=None)
|
|
|
|
# one targeted message should have been created
|
|
self.assertEqual(1, mock_msg.call_count)
|
|
|
|
def test_populate_instance_for_create(self):
|
|
super(CellsComputeAPITestCase, self).test_populate_instance_for_create(
|
|
num_instances=2)
|
|
|
|
def test_multi_instance_display_name(self):
|
|
super(CellsComputeAPITestCase,
|
|
self).test_multi_instance_display_name(cells_enabled=True)
|
|
|
|
|
|
class CellsShelveComputeAPITestCase(test_shelve.ShelveComputeAPITestCase):
|
|
def setUp(self):
|
|
super(CellsShelveComputeAPITestCase, self).setUp()
|
|
global ORIG_COMPUTE_API
|
|
ORIG_COMPUTE_API = self.compute_api
|
|
self.compute_api = compute_cells_api.ComputeCellsAPI()
|
|
|
|
def _fake_validate_cell(*args, **kwargs):
|
|
return
|
|
|
|
self.stub_out('nova.compute.api.API._validate_cell',
|
|
_fake_validate_cell)
|
|
|
|
def _create_fake_instance_obj(self, params=None, type_name='m1.tiny',
|
|
services=False, context=None):
|
|
flavor = flavors.get_flavor_by_name(type_name)
|
|
inst = objects.Instance(context=context or self.context)
|
|
inst.cell_name = 'api!child'
|
|
inst.vm_state = vm_states.ACTIVE
|
|
inst.task_state = None
|
|
inst.power_state = power_state.RUNNING
|
|
inst.image_ref = FAKE_IMAGE_REF
|
|
inst.reservation_id = 'r-fakeres'
|
|
inst.user_id = self.user_id
|
|
inst.project_id = self.project_id
|
|
inst.host = self.compute.host
|
|
inst.node = NODENAME
|
|
inst.instance_type_id = flavor.id
|
|
inst.ami_launch_index = 0
|
|
inst.memory_mb = 0
|
|
inst.vcpus = 0
|
|
inst.root_gb = 0
|
|
inst.ephemeral_gb = 0
|
|
inst.architecture = obj_fields.Architecture.X86_64
|
|
inst.os_type = 'Linux'
|
|
inst.system_metadata = (
|
|
params and params.get('system_metadata', {}) or {})
|
|
inst.locked = False
|
|
inst.created_at = timeutils.utcnow()
|
|
inst.updated_at = timeutils.utcnow()
|
|
inst.launched_at = timeutils.utcnow()
|
|
inst.security_groups = objects.SecurityGroupList(objects=[])
|
|
inst.flavor = flavor
|
|
inst.old_flavor = None
|
|
inst.new_flavor = None
|
|
if params:
|
|
inst.flavor.update(params.pop('flavor', {}))
|
|
inst.update(params)
|
|
inst.create()
|
|
|
|
return inst
|
|
|
|
def _test_shelve(self, vm_state=vm_states.ACTIVE,
|
|
boot_from_volume=False, clean_shutdown=True):
|
|
params = dict(task_state=None, vm_state=vm_state,
|
|
display_name='fake-name')
|
|
instance = self._create_fake_instance_obj(params=params)
|
|
with mock.patch.object(self.compute_api,
|
|
'_cast_to_cells') as cast_to_cells:
|
|
self.compute_api.shelve(self.context, instance,
|
|
clean_shutdown=clean_shutdown)
|
|
cast_to_cells.assert_called_once_with(self.context,
|
|
instance, 'shelve',
|
|
clean_shutdown=clean_shutdown
|
|
)
|
|
|
|
def test_unshelve(self):
|
|
# Ensure instance can be unshelved on cell environment.
|
|
# The super class tests nova-shelve.
|
|
instance = self._create_fake_instance_obj()
|
|
|
|
self.assertIsNone(instance['task_state'])
|
|
|
|
self.compute_api.shelve(self.context, instance)
|
|
|
|
instance.task_state = None
|
|
instance.vm_state = vm_states.SHELVED
|
|
instance.save()
|
|
|
|
with mock.patch.object(self.compute_api,
|
|
'_cast_to_cells') as cast_to_cells:
|
|
self.compute_api.unshelve(self.context, instance)
|
|
cast_to_cells.assert_called_once_with(self.context,
|
|
instance, 'unshelve')
|
|
|
|
def tearDown(self):
|
|
global ORIG_COMPUTE_API
|
|
self.compute_api = ORIG_COMPUTE_API
|
|
super(CellsShelveComputeAPITestCase, self).tearDown()
|
|
|
|
|
|
class CellsConductorAPIRPCRedirect(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(CellsConductorAPIRPCRedirect, self).setUp()
|
|
|
|
self.compute_api = compute_cells_api.ComputeCellsAPI()
|
|
self.cells_rpcapi = mock.MagicMock()
|
|
self.compute_api.compute_task_api.cells_rpcapi = self.cells_rpcapi
|
|
|
|
self.context = context.RequestContext('fake', 'fake')
|
|
|
|
@mock.patch.object(compute_api.API, '_record_action_start')
|
|
@mock.patch.object(compute_api.API, '_provision_instances')
|
|
@mock.patch.object(compute_api.API, '_check_and_transform_bdm')
|
|
@mock.patch.object(compute_api.API, '_get_image')
|
|
@mock.patch.object(compute_api.API, '_validate_and_build_base_options')
|
|
@mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild')
|
|
def test_build_instances(self, _checks_for_create_and_rebuild,
|
|
_validate, _get_image, _check_bdm,
|
|
_provision, _record_action_start):
|
|
_get_image.return_value = (None, 'fake-image')
|
|
_validate.return_value = ({}, 1, None, ['default'], None)
|
|
_check_bdm.return_value = objects.BlockDeviceMappingList()
|
|
_provision.return_value = []
|
|
|
|
with mock.patch.object(self.compute_api.compute_task_api,
|
|
'schedule_and_build_instances') as sbi:
|
|
self.compute_api.create(self.context, 'fake-flavor', 'fake-image')
|
|
|
|
# Subsequent tests in class are verifying the hooking. We
|
|
# don't check args since this is verified in compute test
|
|
# code.
|
|
self.assertTrue(sbi.called)
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_flavor_image_nostatus')
|
|
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
|
@mock.patch.object(compute_api.API, '_record_action_start')
|
|
@mock.patch.object(compute_api.API, '_resize_cells_support')
|
|
@mock.patch.object(compute_utils, 'upsize_quota_delta')
|
|
@mock.patch.object(objects.Instance, 'save')
|
|
@mock.patch.object(flavors, 'extract_flavor')
|
|
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
def test_resize_instance(self, _bdms, _check, _extract, _save, _upsize,
|
|
_cells, _record, _spec_get_by_uuid,
|
|
mock_validate):
|
|
flavor = objects.Flavor(**test_flavor.fake_flavor)
|
|
_extract.return_value = flavor
|
|
orig_system_metadata = {}
|
|
instance = fake_instance.fake_instance_obj(self.context,
|
|
vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
|
launched_at=timeutils.utcnow(),
|
|
system_metadata=orig_system_metadata,
|
|
expected_attrs=['system_metadata'])
|
|
instance.flavor = flavor
|
|
instance.old_flavor = instance.new_flavor = None
|
|
self.compute_api.resize(self.context, instance)
|
|
self.assertTrue(self.cells_rpcapi.resize_instance.called)
|
|
|
|
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
|
|
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
|
@mock.patch.object(compute_api.API, '_record_action_start')
|
|
@mock.patch.object(objects.Instance, 'save')
|
|
def test_live_migrate_instance(self, instance_save, _record, _get_spec,
|
|
mock_nodelist):
|
|
orig_system_metadata = {}
|
|
instance = fake_instance.fake_instance_obj(self.context,
|
|
vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
|
launched_at=timeutils.utcnow(),
|
|
system_metadata=orig_system_metadata,
|
|
expected_attrs=['system_metadata'])
|
|
|
|
self.compute_api.live_migrate(self.context, instance,
|
|
True, True, 'fake_dest_host')
|
|
|
|
self.assertTrue(self.cells_rpcapi.live_migrate_instance.called)
|
|
|
|
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
|
@mock.patch.object(objects.Instance, 'save')
|
|
@mock.patch.object(objects.Instance, 'get_flavor')
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
@mock.patch.object(compute_api.API, '_get_image')
|
|
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
|
@mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild')
|
|
@mock.patch.object(compute_api.API, '_record_action_start')
|
|
def test_rebuild_instance(self, _record_action_start,
|
|
_checks_for_create_and_rebuild, _check_auto_disk_config,
|
|
_get_image, bdm_get_by_instance_uuid, get_flavor, instance_save,
|
|
_req_spec_get_by_inst_uuid):
|
|
orig_system_metadata = {}
|
|
instance = fake_instance.fake_instance_obj(self.context,
|
|
vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
|
launched_at=timeutils.utcnow(), image_ref=uuids.image_id,
|
|
system_metadata=orig_system_metadata,
|
|
expected_attrs=['system_metadata'])
|
|
get_flavor.return_value = {}
|
|
# The API request schema validates that a UUID is passed for the
|
|
# imageRef parameter so we need to provide an image.
|
|
image_href = uuids.image_id
|
|
image = {"min_ram": 10, "min_disk": 1,
|
|
"properties": {'architecture': 'x86_64'},
|
|
"id": uuids.image_id}
|
|
admin_pass = ''
|
|
files_to_inject = []
|
|
bdms = objects.BlockDeviceMappingList()
|
|
|
|
_get_image.return_value = (None, image)
|
|
bdm_get_by_instance_uuid.return_value = bdms
|
|
|
|
self.compute_api.rebuild(self.context, instance, image_href,
|
|
admin_pass, files_to_inject)
|
|
|
|
self.assertTrue(self.cells_rpcapi.rebuild_instance.called)
|
|
|
|
def test_check_equal(self):
|
|
task_api = self.compute_api.compute_task_api
|
|
tests = set()
|
|
for (name, value) in inspect.getmembers(self, inspect.ismethod):
|
|
if name.startswith('test_') and name != 'test_check_equal':
|
|
tests.add(name[5:])
|
|
if tests != set(task_api.cells_compatible):
|
|
self.fail("Testcases not equivalent to cells_compatible list")
|