Move instance creation to conductor

This reworks a big chunk of the instance boot flow. A new method has
been added to the conductor which will handle the initial scheduling and
creation of an instance in the cell db. At that point the request moves
to the compute as normal. A reschedule will call back to the cell
conductor as normal and can not result in a host from another cell being
selected.

Co-Authored-By: Dan Smith <dansmith@redhat.com>
Implements: bp cells-scheduling-interaction
Change-Id: I8742071b55f018f864f5a382de20075a5b444a79
Depends-On: I8938fd37f71498e87aa348a81c357eef5c1a8093
This commit is contained in:
Andrey Volkov 2016-09-29 11:43:09 +03:00 committed by Dan Smith
parent f9d7b383a7
commit 3c2ce9450b
12 changed files with 191 additions and 229 deletions

View File

@ -67,7 +67,6 @@ from nova import network
from nova.network import model as network_model
from nova.network.security_group import openstack_driver
from nova.network.security_group import security_group_base
from nova import notifications
from nova import objects
from nova.objects import base as obj_base
from nova.objects import block_device as block_device_obj
@ -967,6 +966,10 @@ class API(base.Base):
base_options['pci_requests'], filter_properties,
instance_group, base_options['availability_zone'],
security_groups=security_groups)
# NOTE(danms): We need to record num_instances on the request
# spec as this is how the conductor knows how many were in this
# batch.
req_spec.num_instances = num_instances
req_spec.create()
# Create an instance object, but do not store in db yet.
@ -1214,35 +1217,21 @@ class API(base.Base):
key_pair)
instances = []
request_specs = []
build_requests = []
# TODO(alaski): Cast to conductor here which will call the
# scheduler and defer instance creation until the scheduler
# has picked a cell/host. Set the instance_mapping to the cell
# that the instance is scheduled to.
# NOTE(alaski): Instance and block device creation are going
# to move to the conductor.
try:
for rs, build_request, im in instances_to_build:
build_requests.append(build_request)
instance = build_request.get_new_instance(context)
for rs, build_request, im in instances_to_build:
build_requests.append(build_request)
instance = build_request.get_new_instance(context)
instances.append(instance)
request_specs.append(rs)
if CONF.cells.enable:
# NOTE(danms): CellsV1 can't do the new thing, so we
# do the old thing here. We can remove this path once
# we stop supporting v1.
for instance in instances:
instance.create()
instances.append(instance)
self._create_block_device_mapping(
build_request.block_device_mappings)
# send a state update notification for the initial create to
# show it going from non-existent to BUILDING
notifications.send_update_with_states(context, instance, None,
vm_states.BUILDING, None, None, service="api")
except Exception:
with excutils.save_and_reraise_exception():
self._safe_destroy_instance_residue(instances,
instances_to_build)
for instance in instances:
self._record_action_start(context, instance,
instance_actions.CREATE)
self.compute_task_api.build_instances(context,
self.compute_task_api.build_instances(context,
instances=instances, image=boot_meta,
filter_properties=filter_properties,
admin_password=admin_password,
@ -1251,6 +1240,16 @@ class API(base.Base):
security_groups=security_groups,
block_device_mapping=block_device_mapping,
legacy_bdm=False)
else:
self.compute_task_api.schedule_and_build_instances(
context,
build_requests=build_requests,
request_spec=request_specs,
image=boot_meta,
admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
block_device_mapping=block_device_mapping)
return (instances, reservation_id)
@ -1666,6 +1665,7 @@ class API(base.Base):
# to look it up.
# If we're on cellsv1, we can't yet short-circuit the cells
# messaging path
cell = None
try:
instance = objects.Instance.get_by_uuid(context, uuid)
except exception.InstanceNotFound:
@ -1673,9 +1673,10 @@ class API(base.Base):
# instance to the database and hasn't done that yet. It's up to
# the caller of this method to determine what to do with that
# information.
return
return None, None
else:
with nova_context.target_cell(context, inst_map.cell_mapping):
cell = inst_map.cell_mapping
with nova_context.target_cell(context, cell):
try:
instance = objects.Instance.get_by_uuid(context,
uuid)
@ -1683,8 +1684,8 @@ class API(base.Base):
# Since the cell_mapping exists we know the instance is in
# the cell, however InstanceNotFound means it's already
# deleted.
return
return instance
return None, None
return cell, instance
def _delete_while_booting(self, context, instance):
"""Handle deletion if the instance has not reached a cell yet
@ -1734,10 +1735,14 @@ class API(base.Base):
# Look up the instance because the current instance object was
# stashed on the buildrequest and therefore not complete enough
# to run .destroy().
instance = self._lookup_instance(context, instance.uuid)
cell, instance = self._lookup_instance(context, instance.uuid)
if instance is not None:
# If instance is None it has already been deleted.
instance.destroy()
if cell:
with nova_context.target_cell(context, cell):
instance.destroy()
else:
instance.destroy()
except exception.InstanceNotFound:
quotas.rollback()
@ -1769,19 +1774,36 @@ class API(base.Base):
# sent to a cell/compute which means it was pulled from the cell db.
# Normal delete should be attempted.
if not instance.host:
if self._delete_while_booting(context, instance):
return
# If instance.host was not set it's possible that the Instance
# object here was pulled from a BuildRequest object and is not
# fully populated. Notably it will be missing an 'id' field which
# will prevent instance.destroy from functioning properly. A lookup
# is attempted which will either return a full Instance or None if
# not found. If not found then it's acceptable to skip the rest of
# the delete processing.
instance = self._lookup_instance(context, instance.uuid)
if not instance:
# Instance is already deleted.
return
try:
if self._delete_while_booting(context, instance):
return
# If instance.host was not set it's possible that the Instance
# object here was pulled from a BuildRequest object and is not
# fully populated. Notably it will be missing an 'id' field
# which will prevent instance.destroy from functioning
# properly. A lookup is attempted which will either return a
# full Instance or None if not found. If not found then it's
# acceptable to skip the rest of the delete processing.
cell, instance = self._lookup_instance(context, instance.uuid)
if cell and instance:
with nova_context.target_cell(context, cell):
instance.destroy()
return
if not instance:
# Instance is already deleted.
return
except exception.ObjectActionError:
# NOTE(melwitt): This means the instance.host changed
# under us indicating the instance became scheduled
# during the destroy(). Refresh the instance from the DB and
# continue on with the delete logic for a scheduled instance.
# NOTE(danms): If instance.host is set, we should be able to
# do the following lookup. If not, there's not much we can
# do to recover.
cell, instance = self._lookup_instance(context, instance.uuid)
if not instance:
# Instance is already deleted
return
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance.uuid)
@ -2497,8 +2519,7 @@ class API(base.Base):
if not CELLS:
CELLS = objects.CellMappingList.get_all(context)
LOG.debug('Found %(count)i cells: %(cells)s',
count=len(CELLS),
cells=CELLS)
dict(count=len(CELLS), cells=CELLS))
if not CELLS:
LOG.error(_LE('No cells are configured, unable to list instances'))

View File

@ -232,7 +232,8 @@ class ComputeCellsAPI(compute_api.API):
# set now. We handle this similarly to how the
# ObjectActionError is handled below.
with excutils.save_and_reraise_exception() as exc:
instance = self._lookup_instance(context, instance.uuid)
_cell, instance = self._lookup_instance(context,
instance.uuid)
if instance is None:
exc.reraise = False
elif instance.cell_name:
@ -247,7 +248,7 @@ class ComputeCellsAPI(compute_api.API):
# lookup is attempted which will either return a full Instance or
# None if not found. If not found then it's acceptable to skip the
# rest of the delete processing.
instance = self._lookup_instance(context, instance.uuid)
_cell, instance = self._lookup_instance(context, instance.uuid)
if instance is None:
# Instance has been deleted out from under us
return

View File

@ -933,6 +933,14 @@ class ComputeTaskManager(base.Base):
instance.destroy()
except exception.InstanceNotFound:
pass
except exception.ObjectActionError:
# NOTE(melwitt): Instance became scheduled during
# the destroy, "host changed". Refresh and re-destroy.
try:
instance.refresh()
instance.destroy()
except exception.InstanceNotFound:
pass
for bdm in instance_bdms:
with obj_target_cell(bdm, cell):
try:

View File

@ -155,11 +155,11 @@ class TestInstanceNotificationSample(
instance_updates = self._get_notifications('instance.update')
# The first notification comes from the nova-api the rest is from the
# nova-compute. To keep the test simpler assert this fact and then
# modify the publisher_id of the first notification to match the
# template
self.assertEqual('nova-api:fake-mini',
# The first notification comes from the nova-conductor the
# rest is from the nova-compute. To keep the test simpler
# assert this fact and then modify the publisher_id of the
# first notification to match the template
self.assertEqual('conductor:fake-mini',
instance_updates[0]['publisher_id'])
instance_updates[0]['publisher_id'] = 'nova-compute:fake-mini'

View File

@ -44,6 +44,7 @@ class ServersPreSchedulingTestCase(test.TestCase):
self.api = api_fixture.api
self.api.microversion = 'latest'
self.useFixture(nova_fixtures.SingleCellSimple())
def test_instance_from_buildrequest(self):
self.useFixture(nova_fixtures.AllServicesCurrent())

View File

@ -21,14 +21,12 @@ from nova.api.openstack.compute import extension_info
from nova.api.openstack.compute import servers as servers_v21
from nova import availability_zones
from nova.compute import api as compute_api
from nova.compute import flavors
from nova import context
from nova import db
from nova import exception
from nova import servicegroup
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_instance
from nova.tests.unit.image import fake
from nova.tests.unit import matchers
from nova.tests.unit.objects import test_service
@ -187,37 +185,14 @@ class ServersControllerCreateTestV21(test.TestCase):
fakes.stub_out_nw_api(self)
self._set_up_controller()
def instance_create(context, inst):
inst_type = flavors.get_flavor_by_flavor_id(3)
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def_image_ref = 'http://localhost/images/%s' % image_uuid
self.instance_cache_num += 1
instance = fake_instance.fake_db_instance(**{
'id': self.instance_cache_num,
'display_name': inst['display_name'] or 'test',
'uuid': FAKE_UUID,
'instance_type': inst_type,
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': inst.get('image_ref', def_image_ref),
'user_id': 'fake',
'project_id': 'fake',
'availability_zone': 'nova',
'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"progress": 0,
"fixed_ips": [],
"task_state": "",
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
})
def create_db_entry_for_new_instance(*args, **kwargs):
instance = args[4]
instance.uuid = FAKE_UUID
return instance
fake.stub_out_image_service(self)
self.stub_out('nova.db.instance_create', instance_create)
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
create_db_entry_for_new_instance)
self.req = fakes.HTTPRequest.blank('')
def _set_up_controller(self):

View File

@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
@ -22,12 +20,10 @@ from oslo_serialization import jsonutils
from nova.api.openstack.compute import extension_info
from nova.api.openstack.compute import servers as servers_v21
from nova.compute import api as compute_api
from nova.compute import flavors
from nova import exception
from nova import objects
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_instance
from nova.tests.unit.image import fake
from nova.tests import uuidsentinel as uuids
@ -109,35 +105,15 @@ class ServersControllerCreateTestV21(test.TestCase):
fakes.stub_out_nw_api(self)
self._set_up_controller()
def instance_create(context, inst):
inst_type = flavors.get_flavor_by_flavor_id(3)
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def_image_ref = 'http://localhost/images/%s' % image_uuid
self.instance_cache_num += 1
instance = fake_instance.fake_db_instance(**{
'id': self.instance_cache_num,
'display_name': inst['display_name'] or 'test',
'uuid': fakes.FAKE_UUID,
'instance_type': inst_type,
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': inst.get('image_ref', def_image_ref),
'user_id': 'fake',
'project_id': 'fake',
'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"progress": 0,
"fixed_ips": [],
"task_state": "",
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
})
fake.stub_out_image_service(self)
def create_db_entry_for_new_instance(*args, **kwargs):
instance = args[4]
instance.uuid = fakes.FAKE_UUID
return instance
fake.stub_out_image_service(self)
self.stub_out('nova.db.instance_create', instance_create)
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
create_db_entry_for_new_instance)
def _test_create_extra(self, params, override_controller):
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'

View File

@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import webob
from nova.api.openstack.compute import block_device_mapping \
@ -23,12 +21,10 @@ from nova.api.openstack.compute import extension_info
from nova.api.openstack.compute import multiple_create as multiple_create_v21
from nova.api.openstack.compute import servers as servers_v21
from nova.compute import api as compute_api
from nova.compute import flavors
import nova.conf
from nova import exception
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_instance
from nova.tests.unit.image import fake
CONF = nova.conf.CONF
@ -62,35 +58,6 @@ class MultiCreateExtensionTestV21(test.TestCase):
self.no_mult_create_controller = servers_v21.ServersController(
extension_info=ext_info)
def instance_create(context, inst):
inst_type = flavors.get_flavor_by_flavor_id(3)
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def_image_ref = 'http://localhost/images/%s' % image_uuid
self.instance_cache_num += 1
instance = fake_instance.fake_db_instance(**{
'id': self.instance_cache_num,
'display_name': inst['display_name'] or 'test',
'uuid': inst['uuid'],
'instance_type': inst_type,
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': inst.get('image_ref', def_image_ref),
'user_id': 'fake',
'project_id': 'fake',
'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"progress": 0,
"fixed_ips": [],
"task_state": "",
"vm_state": "",
"security_groups": inst['security_groups'],
})
self.instance_cache_by_id[instance['id']] = instance
self.instance_cache_by_uuid[instance['uuid']] = instance
return instance
def instance_get(context, instance_id):
"""Stub for compute/api create() pulling in instance after
scheduling
@ -114,12 +81,18 @@ class MultiCreateExtensionTestV21(test.TestCase):
def project_get_networks(context, user_id):
return dict(id='1', host='localhost')
def create_db_entry_for_new_instance(*args, **kwargs):
instance = args[4]
self.instance_cache_by_uuid[instance.uuid] = instance
return instance
fakes.stub_out_key_pair_funcs(self)
fake.stub_out_image_service(self)
self.stub_out('nova.db.instance_add_security_group',
return_security_group)
self.stub_out('nova.db.project_get_networks', project_get_networks)
self.stub_out('nova.db.instance_create', instance_create)
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
create_db_entry_for_new_instance)
self.stub_out('nova.db.instance_system_metadata_update', fake_method)
self.stub_out('nova.db.instance_get', instance_get)
self.stub_out('nova.db.instance_update', instance_update)

View File

@ -7798,17 +7798,18 @@ class ComputeAPITestCase(BaseTestCase):
self.fake_show = fake_show
def fake_lookup(self, context, instance):
return instance
return None, instance
self.stub_out('nova.compute.api.API._lookup_instance', fake_lookup)
# Mock out build_instances and rebuild_instance since nothing in these
# tests should need those to actually run. We do this to avoid
# possible races with other tests that actually test those methods
# and mock things out within them, like conductor tests.
self.build_instances_mock = mock.Mock(autospec=True)
self.compute_api.compute_task_api.build_instances = \
self.build_instances_mock
# Mock out schedule_and_build_instances and rebuild_instance
# since nothing in these tests should need those to actually
# run. We do this to avoid possible races with other tests
# that actually test those methods and mock things out within
# them, like conductor tests.
self.schedule_and_build_instances_mock = mock.Mock(autospec=True)
self.compute_api.compute_task_api.schedule_and_build_instances = \
self.schedule_and_build_instances_mock
self.rebuild_instance_mock = mock.Mock(autospec=True)
self.compute_api.compute_task_api.rebuild_instance = \
@ -7921,30 +7922,6 @@ class ComputeAPITestCase(BaseTestCase):
(refs, resv_id) = self.compute_api.create(self.context,
inst_type, self.fake_image['id'])
def test_create_bdm_from_flavor(self):
instance_type_params = {
'flavorid': 'test', 'name': 'test',
'swap': 1024, 'ephemeral_gb': 1, 'root_gb': 1,
}
self._create_instance_type(params=instance_type_params)
inst_type = flavors.get_flavor_by_name('test')
self.stub_out('nova.tests.unit.image.fake._FakeImageService.show',
self.fake_show)
(refs, resv_id) = self.compute_api.create(self.context, inst_type,
self.fake_image['id'])
instance_uuid = refs[0]['uuid']
bdms = block_device_obj.BlockDeviceMappingList.get_by_instance_uuid(
self.context, instance_uuid)
ephemeral = list(filter(block_device.new_format_is_ephemeral, bdms))
self.assertEqual(1, len(ephemeral))
swap = list(filter(block_device.new_format_is_swap, bdms))
self.assertEqual(1, len(swap))
self.assertEqual(1024, swap[0].volume_size)
self.assertEqual(1, ephemeral[0].volume_size)
def test_create_with_deleted_image(self):
# If we're given a deleted image by glance, we should not be able to
# build from it
@ -7998,53 +7975,54 @@ class ComputeAPITestCase(BaseTestCase):
def test_create_instance_sets_system_metadata(self):
# Make sure image properties are copied into system metadata.
(ref, resv_id) = self.compute_api.create(
with mock.patch.object(self.compute_api.compute_task_api,
'schedule_and_build_instances') as mock_sbi:
(ref, resv_id) = self.compute_api.create(
self.context,
instance_type=flavors.get_default_flavor(),
image_href='f5000000-0000-0000-0000-000000000000')
sys_metadata = db.instance_system_metadata_get(self.context,
ref[0]['uuid'])
build_call = mock_sbi.call_args_list[0]
instance = build_call[1]['build_requests'][0].instance
image_props = {'image_kernel_id': uuids.kernel_id,
'image_ramdisk_id': uuids.ramdisk_id,
'image_something_else': 'meow', }
for key, value in six.iteritems(image_props):
self.assertIn(key, sys_metadata)
self.assertEqual(value, sys_metadata[key])
self.assertIn(key, instance.system_metadata)
self.assertEqual(value, instance.system_metadata[key])
def test_create_saves_flavor(self):
instance_type = flavors.get_default_flavor()
(ref, resv_id) = self.compute_api.create(
with mock.patch.object(self.compute_api.compute_task_api,
'schedule_and_build_instances') as mock_sbi:
instance_type = flavors.get_default_flavor()
(ref, resv_id) = self.compute_api.create(
self.context,
instance_type=instance_type,
image_href=uuids.image_href_id)
instance = objects.Instance.get_by_uuid(self.context, ref[0]['uuid'])
build_call = mock_sbi.call_args_list[0]
instance = build_call[1]['build_requests'][0].instance
self.assertIn('flavor', instance)
self.assertEqual(instance_type.flavorid, instance.flavor.flavorid)
self.assertNotIn('instance_type_id', instance.system_metadata)
def test_create_instance_associates_security_groups(self):
# Make sure create associates security groups.
group = self._create_group()
(ref, resv_id) = self.compute_api.create(
with mock.patch.object(self.compute_api.compute_task_api,
'schedule_and_build_instances') as mock_sbi:
(ref, resv_id) = self.compute_api.create(
self.context,
instance_type=flavors.get_default_flavor(),
image_href=uuids.image_href_id,
security_groups=['testgroup'])
groups_for_instance = db.security_group_get_by_instance(
self.context, ref[0]['uuid'])
# For Neutron we don't store the security groups in the nova database.
if CONF.use_neutron:
self.assertEqual(0, len(groups_for_instance))
else:
self.assertEqual(1, len(groups_for_instance))
self.assertEqual(group.id, groups_for_instance[0].id)
group_with_instances = db.security_group_get(self.context,
group.id,
columns_to_join=['instances'])
self.assertEqual(1, len(group_with_instances.instances))
build_call = mock_sbi.call_args_list[0]
reqspec = build_call[1]['request_spec'][0]
self.assertEqual(1, len(reqspec.security_groups))
self.assertEqual(group.name, reqspec.security_groups[0].name)
def test_create_instance_with_invalid_security_group_raises(self):
instance_type = flavors.get_default_flavor()
@ -9257,7 +9235,7 @@ class ComputeAPITestCase(BaseTestCase):
self.compute_api.remove_fixed_ip(self.context,
instance, '192.168.1.1')
with mock.patch.object(self.compute_api, '_lookup_instance',
return_value=instance):
return_value=(None, instance)):
with mock.patch.object(self.compute_api.network_api,
'deallocate_for_instance'):
self.compute_api.delete(self.context, instance)

View File

@ -1177,7 +1177,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.mox.StubOutWithMock(rpcapi, 'terminate_instance')
self.compute_api._lookup_instance(self.context,
inst.uuid).AndReturn(inst)
inst.uuid).AndReturn((None, inst))
objects.BlockDeviceMappingList.get_by_instance_uuid(
self.context, inst.uuid).AndReturn(
objects.BlockDeviceMappingList())
@ -1451,7 +1451,7 @@ class _ComputeAPIUnitTestMixIn(object):
@mock.patch.object(self.compute_api, '_attempt_delete_of_buildrequest',
return_value=True)
@mock.patch.object(self.compute_api, '_lookup_instance',
return_value=None)
return_value=(None, None))
@mock.patch.object(self.compute_api, '_create_reservations',
return_value=quota_mock)
def test(mock_create_res, mock_lookup, mock_attempt):
@ -1493,9 +1493,9 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance) as mock_inst_get:
ret_instance = self.compute_api._lookup_instance(self.context,
instance.uuid)
self.assertEqual(instance, ret_instance)
cell, ret_instance = self.compute_api._lookup_instance(
self.context, instance.uuid)
self.assertEqual((None, instance), (cell, ret_instance))
mock_inst_get.assert_called_once_with(self.context, instance.uuid)
self.assertFalse(mock_target_cell.called)
@ -1508,9 +1508,9 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance) as mock_inst_get:
ret_instance = self.compute_api._lookup_instance(self.context,
instance.uuid)
self.assertEqual(instance, ret_instance)
cell, ret_instance = self.compute_api._lookup_instance(
self.context, instance.uuid)
self.assertEqual((None, instance), (cell, ret_instance))
mock_inst_get.assert_called_once_with(self.context, instance.uuid)
self.assertFalse(mock_target_cell.called)
@ -1527,9 +1527,12 @@ class _ComputeAPIUnitTestMixIn(object):
@mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance)
def test(mock_inst_get, mock_map_get):
ret_instance = self.compute_api._lookup_instance(self.context,
instance.uuid)
self.assertEqual(instance, ret_instance)
cell, ret_instance = self.compute_api._lookup_instance(
self.context, instance.uuid)
expected_cell = (self.cell_type is None and
inst_map.cell_mapping or None)
self.assertEqual((expected_cell, instance),
(cell, ret_instance))
mock_inst_get.assert_called_once_with(self.context, instance.uuid)
if self.cell_type is None:
mock_target_cell.assert_called_once_with(self.context,
@ -3487,6 +3490,7 @@ class _ComputeAPIUnitTestMixIn(object):
do_test()
def test_provision_instances_creates_build_request(self):
@mock.patch.object(objects.Instance, 'create')
@mock.patch.object(self.compute_api, 'volume_api')
@mock.patch.object(self.compute_api, '_check_num_instances_quota')
@mock.patch.object(self.compute_api.security_group_api,
@ -3496,7 +3500,7 @@ class _ComputeAPIUnitTestMixIn(object):
@mock.patch.object(objects.InstanceMapping, 'create')
def do_test(_mock_inst_mapping_create, mock_build_req,
mock_req_spec_from_components, _mock_ensure_default,
mock_check_num_inst_quota, mock_volume):
mock_check_num_inst_quota, mock_volume, mock_inst_create):
min_count = 1
max_count = 2

View File

@ -147,9 +147,18 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
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.object(compute_api.API, '_local_delete')
@mock.patch.object(compute_api.API, '_lookup_instance',
return_value=None)
return_value=(None, None))
def test_delete_instance_no_cell_instance_disappear(self, mock_lookup,
mock_local_delete):
inst = self._create_fake_instance_obj()
@ -175,7 +184,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
cells_rpcapi.instance_delete_everywhere(self.context,
inst, delete_type)
compute_api.API._lookup_instance(self.context,
inst.uuid).AndReturn(inst)
inst.uuid).AndReturn((None, inst))
compute_api.API._local_delete(self.context, inst,
mox.IsA(objects.BlockDeviceMappingList),
method_name, mox.IgnoreArg())
@ -196,7 +205,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
@mock.patch.object(self.compute_api.cells_rpcapi,
'instance_delete_everywhere')
@mock.patch.object(compute_api.API, '_lookup_instance',
return_value=inst)
return_value=(None, inst))
def _test(_mock_lookup_inst, _mock_delete_everywhere):
self.assertRaises(exception.ObjectActionError,
self.compute_api.delete, self.context, inst)
@ -221,7 +230,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
@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=inst)
return_value=(None, inst))
def _test(_mock_lookup_inst, mock_delete_everywhere,
mock_compute_delete):
self.compute_api.delete(self.context, inst)
@ -249,7 +258,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
side_effect=actionerror)
@mock.patch.object(instance, 'refresh', side_effect=notfound)
@mock.patch.object(compute_api.API, '_lookup_instance',
return_value=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)
@ -276,7 +285,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
@mock.patch.object(self.compute_api.cells_rpcapi,
'instance_delete_everywhere')
@mock.patch.object(compute_api.API, '_lookup_instance',
return_value=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,
@ -315,7 +324,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
instance = self._create_fake_instance_obj()
instance_with_cell = copy.deepcopy(instance)
instance_with_cell.cell_name = 'foo'
mock_lookup_instance.return_value = instance_with_cell
mock_lookup_instance.return_value = None, instance_with_cell
cells_rpcapi = self.compute_api.cells_rpcapi
@ -341,7 +350,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
# 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
mock_lookup_instance.return_value = None, None
cells_rpcapi = self.compute_api.cells_rpcapi
@ -495,11 +504,14 @@ class CellsConductorAPIRPCRedirect(test.NoDBTestCase):
_check_bdm.return_value = objects.BlockDeviceMappingList()
_provision.return_value = []
self.compute_api.create(self.context, 'fake-flavor', 'fake-image')
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(self.cells_rpcapi.build_instances.called)
# 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')

View File

@ -24,6 +24,7 @@ from oslo_utils import timeutils
from oslo_versionedobjects import exception as ovo_exc
import six
from nova import block_device
from nova.compute import flavors
from nova.compute import rpcapi as compute_rpcapi
from nova.compute import task_states
@ -1420,7 +1421,7 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
source_type='blank', destination_type='local',
guest_format='foo', device_type='disk', disk_bus='',
boot_index=1, device_name='xvda', delete_on_termination=False,
snapshot_id=None, volume_id=None, volume_size=0,
snapshot_id=None, volume_id=None, volume_size=1,
image_id='bar', no_device=False, connection_info=None,
tag=''))
params['block_device_mapping'] = objects.BlockDeviceMappingList(
@ -1435,8 +1436,10 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
'nodename': 'fake-nodename',
'limits': None}]
params = self.params
details = {}
def _build_and_run_instance(ctxt, *args, **kwargs):
details['instance'] = kwargs['instance']
self.assertTrue(kwargs['instance'].id)
self.assertEqual(1, len(kwargs['block_device_mapping']))
# FIXME(danms): How to validate the db connection here?
@ -1446,6 +1449,16 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
self.conductor.schedule_and_build_instances(**params)
self.assertTrue(build_and_run_instance.called)
instance_uuid = details['instance'].uuid
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
self.context, instance_uuid)
ephemeral = list(filter(block_device.new_format_is_ephemeral, bdms))
self.assertEqual(1, len(ephemeral))
swap = list(filter(block_device.new_format_is_swap, bdms))
self.assertEqual(0, len(swap))
self.assertEqual(1, ephemeral[0].volume_size)
@mock.patch('nova.compute.rpcapi.ComputeAPI.build_and_run_instance')
@mock.patch('nova.scheduler.rpcapi.SchedulerAPI.select_destinations')
@mock.patch('nova.objects.HostMapping.get_by_host')