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:
parent
f9d7b383a7
commit
3c2ce9450b
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue