4e36c4bd0e
A volume-backed instance will not have the instance.image_ref attribute set, to indicate to the API user that it is a volume-backed instance. Commit984dd8ad6a
missed this subtle difference with how instance.image_ref is used, which means a rebuild of any volume-backed instance will now run through the scheduler, even if the image_href passed to rebuild is the same image ID as for the root volume. This fixes that case in rebuild by getting the image metadata off the root volume for a volume-backed instance and compares that to the image_href passed to rebuild. Change-Id: I48cda813b9effa37f6c3e0cd2e8a22bb78c79d72 Closes-Bug: #1732947 (cherry picked from commit54407afef3
)
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 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
|
|
import nova.conf
|
|
from nova import context
|
|
from nova import 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
|
|
from nova.tests import uuidsentinel as uuids
|
|
|
|
|
|
ORIG_COMPUTE_API = None
|
|
CONF = nova.conf.CONF
|
|
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_class=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_class=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))
|
|
def _test(_mock_lookup_inst, _mock_delete_everywhere):
|
|
self.assertRaises(exception.ObjectActionError,
|
|
self.compute_api.delete, self.context, inst)
|
|
inst.destroy.assert_called_once_with()
|
|
|
|
_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))
|
|
def _test(_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)
|
|
|
|
_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': 'fake-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_default(self):
|
|
self._multi_instance_display_name_default(cells_enabled=True)
|
|
|
|
def test_multi_instance_display_name_template(self):
|
|
super(CellsComputeAPITestCase,
|
|
self).test_multi_instance_display_name_template(
|
|
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'])
|
|
_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(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, 'reserve_quota_delta')
|
|
@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,
|
|
_reserve, _cells, _record, _spec_get_by_uuid):
|
|
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.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):
|
|
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")
|