OpenStack Compute (Nova)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

6921 lines
330 KiB

#
# 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.
"""Unit tests for compute API."""
import contextlib
import datetime
import ddt
import fixtures
import iso8601
import mock
from oslo_messaging import exceptions as oslo_exceptions
from oslo_serialization import jsonutils
from oslo_utils import fixture as utils_fixture
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
from nova.compute import api as compute_api
from nova.compute import flavors
from nova.compute import instance_actions
from nova.compute import power_state
from nova.compute import rpcapi as compute_rpcapi
from nova.compute import task_states
from nova.compute import utils as compute_utils
from nova.compute import vm_states
from nova import conductor
import nova.conf
from nova import context
from nova.db import api as db
from nova import exception
from nova.image import api as image_api
from nova.network import model
from nova.network.neutronv2 import api as neutron_api
from nova.network.neutronv2 import constants
from nova import objects
from nova.objects import base as obj_base
from nova.objects import block_device as block_device_obj
from nova.objects import fields as fields_obj
from nova.objects import quotas as quotas_obj
from nova.objects import security_group as secgroup_obj
from nova.servicegroup import api as servicegroup_api
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_build_request
from nova.tests.unit import fake_instance
from nova.tests.unit import fake_volume
from nova.tests.unit.image import fake as fake_image
from nova.tests.unit import matchers
from nova.tests.unit.objects import test_flavor
from nova.tests.unit.objects import test_migration
from nova import utils
from nova.volume import cinder
CONF = nova.conf.CONF
FAKE_IMAGE_REF = 'fake-image-ref'
NODENAME = 'fakenode1'
SHELVED_IMAGE = 'fake-shelved-image'
SHELVED_IMAGE_NOT_FOUND = 'fake-shelved-image-notfound'
SHELVED_IMAGE_NOT_AUTHORIZED = 'fake-shelved-image-not-authorized'
SHELVED_IMAGE_EXCEPTION = 'fake-shelved-image-exception'
@ddt.ddt
class _ComputeAPIUnitTestMixIn(object):
def setUp(self):
super(_ComputeAPIUnitTestMixIn, self).setUp()
self.user_id = 'fake'
self.project_id = 'fake'
self.compute_api = compute_api.API()
self.context = context.RequestContext(self.user_id,
self.project_id)
def _get_vm_states(self, exclude_states=None):
vm_state = set([vm_states.ACTIVE, vm_states.BUILDING, vm_states.PAUSED,
vm_states.SUSPENDED, vm_states.RESCUED, vm_states.STOPPED,
vm_states.RESIZED, vm_states.SOFT_DELETED,
vm_states.DELETED, vm_states.ERROR, vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED])
if not exclude_states:
exclude_states = set()
return vm_state - exclude_states
def _create_flavor(self, **updates):
flavor = {'id': 1,
'flavorid': 1,
'name': 'm1.tiny',
'memory_mb': 512,
'vcpus': 1,
'vcpu_weight': None,
'root_gb': 1,
'ephemeral_gb': 0,
'rxtx_factor': 1,
'swap': 0,
'deleted': 0,
'disabled': False,
'is_public': True,
'deleted_at': None,
'created_at': datetime.datetime(2012, 1, 19, 18,
49, 30, 877329),
'updated_at': None,
'description': None
}
if updates:
flavor.update(updates)
return objects.Flavor._from_db_object(
self.context, objects.Flavor(extra_specs={}), flavor)
def _create_instance_obj(self, params=None, flavor=None):
"""Create a test instance."""
if not params:
params = {}
if flavor is None:
flavor = self._create_flavor()
now = timeutils.utcnow()
instance = objects.Instance()
instance.metadata = {}
instance.metadata.update(params.pop('metadata', {}))
instance.system_metadata = params.pop('system_metadata', {})
instance._context = self.context
instance.id = 1
instance.uuid = uuidutils.generate_uuid()
instance.vm_state = vm_states.ACTIVE
instance.task_state = None
instance.image_ref = FAKE_IMAGE_REF
instance.reservation_id = 'r-fakeres'
instance.user_id = self.user_id
instance.project_id = self.project_id
instance.host = 'fake_host'
instance.node = NODENAME
instance.instance_type_id = flavor.id
instance.ami_launch_index = 0
instance.memory_mb = 0
instance.vcpus = 0
instance.root_gb = 0
instance.ephemeral_gb = 0
instance.architecture = fields_obj.Architecture.X86_64
instance.os_type = 'Linux'
instance.locked = False
instance.created_at = now
instance.updated_at = now
instance.launched_at = now
instance.disable_terminate = False
instance.info_cache = objects.InstanceInfoCache()
instance.flavor = flavor
instance.old_flavor = instance.new_flavor = None
if params:
instance.update(params)
instance.obj_reset_changes()
return instance
def _create_keypair_obj(self, instance):
"""Create a test keypair."""
keypair = objects.KeyPair()
keypair.id = 1
keypair.name = 'fake_key'
keypair.user_id = instance.user_id
keypair.fingerprint = 'fake'
keypair.public_key = 'fake key'
keypair.type = 'ssh'
return keypair
def _obj_to_list_obj(self, list_obj, obj):
list_obj.objects = []
list_obj.objects.append(obj)
list_obj._context = self.context
list_obj.obj_reset_changes()
return list_obj
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.conductor.conductor_api.ComputeTaskAPI.build_instances')
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch('nova.compute.api.API._check_requested_networks')
@mock.patch('nova.objects.Quotas.limit_check')
@mock.patch('nova.compute.api.API._get_image')
@mock.patch('nova.compute.api.API._provision_instances')
def test_create_with_networks_max_count_none(self, provision_instances,
get_image, check_limit,
check_requested_networks,
record_action_start,
build_instances,
check_deltas):
# Make sure max_count is checked for None, as Python3 doesn't allow
# comparison between NoneType and Integer, something that's allowed in
# Python 2.
provision_instances.return_value = []
get_image.return_value = (None, {})
check_requested_networks.return_value = 1
instance_type = self._create_flavor()
port = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
address = '10.0.0.1'
requested_networks = objects.NetworkRequestList(
objects=[objects.NetworkRequest(address=address,
port_id=port)])
with mock.patch.object(self.compute_api.network_api,
'create_resource_requests',
return_value=(None, [])):
self.compute_api.create(self.context, instance_type, 'image_id',
requested_networks=requested_networks,
max_count=None)
@mock.patch('nova.objects.Quotas.count_as_dict')
@mock.patch('nova.objects.Quotas.limit_check')
@mock.patch('nova.objects.Quotas.limit_check_project_and_user')
def test_create_quota_exceeded_messages(self, mock_limit_check_pu,
mock_limit_check, mock_count):
image_href = "image_href"
image_id = 0
instance_type = self._create_flavor()
quotas = {'instances': 1, 'cores': 1, 'ram': 1}
quota_exception = exception.OverQuota(quotas=quotas,
usages={'instances': 1, 'cores': 1, 'ram': 1},
overs=['instances'])
proj_count = {'instances': 1, 'cores': 1, 'ram': 1}
user_count = proj_count.copy()
mock_count.return_value = {'project': proj_count, 'user': user_count}
# instances/cores/ram quota
mock_limit_check_pu.side_effect = quota_exception
# We don't care about network validation in this test.
self.compute_api.network_api.validate_networks = (
mock.Mock(return_value=40))
with mock.patch.object(self.compute_api, '_get_image',
return_value=(image_id, {})) as mock_get_image:
for min_count, message in [(20, '20-40'), (40, '40')]:
try:
self.compute_api.create(self.context, instance_type,
"image_href", min_count=min_count,
max_count=40)
except exception.TooManyInstances as e:
self.assertEqual(message, e.kwargs['req'])
else:
self.fail("Exception not raised")
mock_get_image.assert_called_with(self.context, image_href)
self.assertEqual(2, mock_get_image.call_count)
self.assertEqual(2, mock_limit_check_pu.call_count)
@mock.patch('nova.objects.Quotas.limit_check')
def test_create_volume_backed_instance_with_trusted_certs(self,
check_limit):
# Creating an instance with no image_ref specified will result in
# creating a volume-backed instance
self.assertRaises(exception.CertificateValidationFailed,
self.compute_api.create, self.context,
instance_type=self._create_flavor(), image_href=None,
trusted_certs=['test-cert-1', 'test-cert-2'])
@mock.patch('nova.objects.Quotas.limit_check')
def test_create_volume_backed_instance_with_conf_trusted_certs(
self, check_limit):
self.flags(verify_glance_signatures=True, group='glance')
self.flags(enable_certificate_validation=True, group='glance')
self.flags(default_trusted_certificate_ids=['certs'], group='glance')
# Creating an instance with no image_ref specified will result in
# creating a volume-backed instance
self.assertRaises(exception.CertificateValidationFailed,
self.compute_api.create, self.context,
instance_type=self._create_flavor(),
image_href=None)
def _test_create_max_net_count(self, max_net_count, min_count, max_count):
with test.nested(
mock.patch.object(self.compute_api, '_get_image',
return_value=(None, {})),
mock.patch.object(self.compute_api, '_check_auto_disk_config'),
mock.patch.object(self.compute_api,
'_validate_and_build_base_options',
return_value=({}, max_net_count, None,
['default'], None))
) as (
get_image,
check_auto_disk_config,
validate_and_build_base_options
):
self.assertRaises(exception.PortLimitExceeded,
self.compute_api.create, self.context, 'fake_flavor',
'image_id', min_count=min_count, max_count=max_count)
def test_max_net_count_zero(self):
# Test when max_net_count is zero.
max_net_count = 0
min_count = 2
max_count = 3
self._test_create_max_net_count(max_net_count, min_count, max_count)
def test_max_net_count_less_than_min_count(self):
# Test when max_net_count is nonzero but less than min_count.
max_net_count = 1
min_count = 2
max_count = 3
self._test_create_max_net_count(max_net_count, min_count, max_count)
def test_specified_port_and_multiple_instances_neutronv2(self):
# Tests that if port is specified there is only one instance booting
# (i.e max_count == 1) as we can't share the same port across multiple
# instances.
self.flags(use_neutron=True)
port = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
address = '10.0.0.1'
min_count = 1
max_count = 2
requested_networks = objects.NetworkRequestList(
objects=[objects.NetworkRequest(address=address,
port_id=port)])
self.assertRaises(exception.MultiplePortsNotApplicable,
self.compute_api.create, self.context, 'fake_flavor', 'image_id',
min_count=min_count, max_count=max_count,
requested_networks=requested_networks)
def _test_specified_ip_and_multiple_instances_helper(self,
requested_networks):
# Tests that if ip is specified there is only one instance booting
# (i.e max_count == 1)
min_count = 1
max_count = 2
self.assertRaises(exception.InvalidFixedIpAndMaxCountRequest,
self.compute_api.create, self.context, "fake_flavor", 'image_id',
min_count=min_count, max_count=max_count,
requested_networks=requested_networks)
def test_specified_ip_and_multiple_instances(self):
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
address = '10.0.0.1'
requested_networks = objects.NetworkRequestList(
objects=[objects.NetworkRequest(network_id=network,
address=address)])
self._test_specified_ip_and_multiple_instances_helper(
requested_networks)
def test_specified_ip_and_multiple_instances_neutronv2(self):
self.flags(use_neutron=True)
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
address = '10.0.0.1'
requested_networks = objects.NetworkRequestList(
objects=[objects.NetworkRequest(network_id=network,
address=address)])
self._test_specified_ip_and_multiple_instances_helper(
requested_networks)
@mock.patch('nova.objects.BlockDeviceMapping.save')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name')
def test_create_volume_bdm_call_reserve_dev_name(self, mock_reserve,
mock_bdm_save):
bdm = objects.BlockDeviceMapping(
**fake_block_device.FakeDbBlockDeviceDict(
{
'id': 1,
'volume_id': 1,
'source_type': 'volume',
'destination_type': 'volume',
'device_name': 'vda',
'boot_index': 1,
}))
mock_reserve.return_value = bdm
instance = self._create_instance_obj()
volume = {'id': '1', 'multiattach': False}
result = self.compute_api._create_volume_bdm(self.context,
instance,
'vda',
volume,
None,
None)
self.assertTrue(mock_reserve.called)
self.assertEqual(result, bdm)
mock_bdm_save.assert_called_once_with()
@mock.patch.object(objects.BlockDeviceMapping, 'create')
def test_create_volume_bdm_local_creation(self, bdm_create):
instance = self._create_instance_obj()
volume_id = 'fake-vol-id'
bdm = objects.BlockDeviceMapping(
**fake_block_device.FakeDbBlockDeviceDict(
{
'instance_uuid': instance.uuid,
'volume_id': volume_id,
'source_type': 'volume',
'destination_type': 'volume',
'device_name': 'vda',
'boot_index': None,
'disk_bus': None,
'device_type': None
}))
result = self.compute_api._create_volume_bdm(self.context,
instance,
'/dev/vda',
{'id': volume_id},
None,
None,
is_local_creation=True)
self.assertEqual(result.instance_uuid, bdm.instance_uuid)
self.assertIsNone(result.device_name)
self.assertEqual(result.volume_id, bdm.volume_id)
self.assertTrue(bdm_create.called)
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name')
@mock.patch.object(objects.BlockDeviceMapping,
'get_by_volume_and_instance')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'attach_volume')
def test_attach_volume_new_flow(self, mock_attach, mock_bdm,
mock_reserve, mock_record):
mock_bdm.side_effect = exception.VolumeBDMNotFound(
volume_id='fake-volume-id')
instance = self._create_instance_obj()
volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol',
None, None, None, None, None)
fake_bdm = mock.MagicMock(spec=objects.BlockDeviceMapping)
mock_reserve.return_value = fake_bdm
mock_volume_api = mock.patch.object(self.compute_api, 'volume_api',
mock.MagicMock(spec=cinder.API))
with mock_volume_api as mock_v_api:
mock_v_api.get.return_value = volume
mock_v_api.attachment_create.return_value = \
{'id': uuids.attachment_id}
self.compute_api.attach_volume(
self.context, instance, volume['id'])
mock_v_api.check_availability_zone.assert_called_once_with(
self.context, volume, instance=instance)
mock_v_api.attachment_create.assert_called_once_with(self.context,
volume['id'],
instance.uuid)
mock_attach.assert_called_once_with(self.context,
instance, fake_bdm)
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name')
@mock.patch.object(objects.BlockDeviceMapping,
'get_by_volume_and_instance')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'attach_volume')
def test_tagged_volume_attach_new_flow(self, mock_attach, mock_bdm,
mock_reserve, mock_record):
mock_bdm.side_effect = exception.VolumeBDMNotFound(
volume_id='fake-volume-id')
instance = self._create_instance_obj()
volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol',
None, None, None, None, None)
fake_bdm = mock.MagicMock(spec=objects.BlockDeviceMapping)
mock_reserve.return_value = fake_bdm
mock_volume_api = mock.patch.object(self.compute_api, 'volume_api',
mock.MagicMock(spec=cinder.API))
with mock_volume_api as mock_v_api:
mock_v_api.get.return_value = volume
mock_v_api.attachment_create.return_value = \
{'id': uuids.attachment_id}
self.compute_api.attach_volume(
self.context, instance, volume['id'], tag='foo')
mock_reserve.assert_called_once_with(self.context, instance, None,
volume['id'],
device_type=None,
disk_bus=None, tag='foo',
multiattach=False)
mock_v_api.check_availability_zone.assert_called_once_with(
self.context, volume, instance=instance)
mock_v_api.attachment_create.assert_called_once_with(
self.context, volume['id'], instance.uuid)
mock_attach.assert_called_once_with(self.context,
instance, fake_bdm)
@mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name')
@mock.patch.object(objects.BlockDeviceMapping,
'get_by_volume_and_instance')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'attach_volume')
def test_attach_volume_attachment_create_fails(self, mock_attach, mock_bdm,
mock_reserve):
mock_bdm.side_effect = exception.VolumeBDMNotFound(
volume_id='fake-volume-id')
instance = self._create_instance_obj()
volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol',
None, None, None, None, None)
fake_bdm = mock.MagicMock(spec=objects.BlockDeviceMapping)
mock_reserve.return_value = fake_bdm
mock_volume_api = mock.patch.object(self.compute_api, 'volume_api',
mock.MagicMock(spec=cinder.API))
with mock_volume_api as mock_v_api:
mock_v_api.get.return_value = volume
mock_v_api.attachment_create.side_effect = test.TestingException()
self.assertRaises(test.TestingException,
self.compute_api.attach_volume,
self.context, instance, volume['id'])
mock_v_api.check_availability_zone.assert_called_once_with(
self.context, volume, instance=instance)
mock_v_api.attachment_create.assert_called_once_with(
self.context, volume['id'], instance.uuid)
self.assertEqual(0, mock_attach.call_count)
fake_bdm.destroy.assert_called_once_with()
def test_suspend(self):
# Ensure instance can be suspended.
instance = self._create_instance_obj()
self.assertEqual(instance.vm_state, vm_states.ACTIVE)
self.assertIsNone(instance.task_state)
rpcapi = self.compute_api.compute_rpcapi
with test.nested(
mock.patch.object(instance, 'save'),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(rpcapi, 'suspend_instance')
) as (mock_inst_save, mock_record_action, mock_suspend_instance):
self.compute_api.suspend(self.context, instance)
self.assertEqual(vm_states.ACTIVE, instance.vm_state)
self.assertEqual(task_states.SUSPENDING, instance.task_state)
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_record_action.assert_called_once_with(
self.context, instance, instance_actions.SUSPEND)
mock_suspend_instance.assert_called_once_with(self.context,
instance)
def _test_suspend_fails(self, vm_state):
params = dict(vm_state=vm_state)
instance = self._create_instance_obj(params=params)
self.assertIsNone(instance.task_state)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.suspend,
self.context, instance)
def test_suspend_fails_invalid_states(self):
invalid_vm_states = self._get_vm_states(set([vm_states.ACTIVE]))
for state in invalid_vm_states:
self._test_suspend_fails(state)
def test_resume(self):
# Ensure instance can be resumed (if suspended).
instance = self._create_instance_obj(
params=dict(vm_state=vm_states.SUSPENDED))
self.assertEqual(instance.vm_state, vm_states.SUSPENDED)
self.assertIsNone(instance.task_state)
rpcapi = self.compute_api.compute_rpcapi
with test.nested(
mock.patch.object(instance, 'save'),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(rpcapi, 'resume_instance')
) as (mock_inst_save, mock_record_action, mock_resume_instance):
self.compute_api.resume(self.context, instance)
self.assertEqual(vm_states.SUSPENDED, instance.vm_state)
self.assertEqual(task_states.RESUMING,
instance.task_state)
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_record_action.assert_called_once_with(
self.context, instance, instance_actions.RESUME)
mock_resume_instance.assert_called_once_with(self.context,
instance)
def test_start(self):
params = dict(vm_state=vm_states.STOPPED)
instance = self._create_instance_obj(params=params)
rpcapi = self.compute_api.compute_rpcapi
with test.nested(
mock.patch.object(instance, 'save'),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(rpcapi, 'start_instance')
) as (mock_inst_save, mock_record_action, mock_start_instance):
self.compute_api.start(self.context, instance)
self.assertEqual(task_states.POWERING_ON,
instance.task_state)
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_record_action.assert_called_once_with(
self.context, instance, instance_actions.START)
mock_start_instance.assert_called_once_with(self.context,
instance)
def test_start_invalid_state(self):
instance = self._create_instance_obj()
self.assertEqual(instance.vm_state, vm_states.ACTIVE)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.start,
self.context, instance)
def test_start_no_host(self):
params = dict(vm_state=vm_states.STOPPED, host='')
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceNotReady,
self.compute_api.start,
self.context, instance)
def _test_stop(self, vm_state, force=False, clean_shutdown=True):
# Make sure 'progress' gets reset
params = dict(task_state=None, progress=99, vm_state=vm_state)
instance = self._create_instance_obj(params=params)
rpcapi = self.compute_api.compute_rpcapi
with test.nested(
mock.patch.object(instance, 'save'),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(rpcapi, 'stop_instance')
) as (mock_inst_save, mock_record_action, mock_stop_instance):
if force:
self.compute_api.force_stop(self.context, instance,
clean_shutdown=clean_shutdown)
else:
self.compute_api.stop(self.context, instance,
clean_shutdown=clean_shutdown)
self.assertEqual(task_states.POWERING_OFF,
instance.task_state)
self.assertEqual(0, instance.progress)
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_record_action.assert_called_once_with(
self.context, instance, instance_actions.STOP)
mock_stop_instance.assert_called_once_with(
self.context, instance, do_cast=True,
clean_shutdown=clean_shutdown)
def test_stop(self):
self._test_stop(vm_states.ACTIVE)
def test_stop_stopped_instance_with_bypass(self):
self._test_stop(vm_states.STOPPED, force=True)
def test_stop_forced_shutdown(self):
self._test_stop(vm_states.ACTIVE, force=True)
def test_stop_without_clean_shutdown(self):
self._test_stop(vm_states.ACTIVE,
clean_shutdown=False)
def test_stop_forced_without_clean_shutdown(self):
self._test_stop(vm_states.ACTIVE, force=True,
clean_shutdown=False)
def _test_stop_invalid_state(self, vm_state):
params = dict(vm_state=vm_state)
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.stop,
self.context, instance)
def test_stop_fails_invalid_states(self):
invalid_vm_states = self._get_vm_states(set([vm_states.ACTIVE,
vm_states.ERROR]))
for state in invalid_vm_states:
self._test_stop_invalid_state(state)
def test_stop_a_stopped_inst(self):
params = {'vm_state': vm_states.STOPPED}
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.stop,
self.context, instance)
def test_stop_no_host(self):
params = {'host': ''}
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceNotReady,
self.compute_api.stop,
self.context, instance)
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch('nova.compute.rpcapi.ComputeAPI.trigger_crash_dump')
def test_trigger_crash_dump(self,
trigger_crash_dump,
_record_action_start):
instance = self._create_instance_obj()
self.compute_api.trigger_crash_dump(self.context, instance)
_record_action_start.assert_called_once_with(self.context, instance,
instance_actions.TRIGGER_CRASH_DUMP)
trigger_crash_dump.assert_called_once_with(self.context, instance)
self.assertIsNone(instance.task_state)
def test_trigger_crash_dump_invalid_state(self):
params = dict(vm_state=vm_states.STOPPED)
instance = self._create_instance_obj(params)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.trigger_crash_dump,
self.context, instance)
def test_trigger_crash_dump_no_host(self):
params = dict(host='')
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceNotReady,
self.compute_api.trigger_crash_dump,
self.context, instance)
def test_trigger_crash_dump_locked(self):
params = dict(locked=True)
instance = self._create_instance_obj(params=params)
self.assertRaises(exception.InstanceIsLocked,
self.compute_api.trigger_crash_dump,
self.context, instance)
def _test_reboot_type(self, vm_state, reboot_type, task_state=None):
# Ensure instance can be soft rebooted.
inst = self._create_instance_obj()
inst.vm_state = vm_state
inst.task_state = task_state
expected_task_state = [None]
if reboot_type == 'HARD':
expected_task_state = task_states.ALLOW_REBOOT
rpcapi = self.compute_api.compute_rpcapi
with test.nested(
mock.patch.object(self.context, 'elevated'),
mock.patch.object(inst, 'save'),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(rpcapi, 'reboot_instance')
) as (mock_elevated, mock_inst_save,
mock_record_action, mock_reboot_instance):
self.compute_api.reboot(self.context, inst, reboot_type)
mock_inst_save.assert_called_once_with(
expected_task_state=expected_task_state)
mock_record_action.assert_called_once_with(
self.context, inst, instance_actions.REBOOT)
mock_reboot_instance.assert_called_once_with(
self.context, instance=inst,
block_device_info=None, reboot_type=reboot_type)
def _test_reboot_type_fails(self, reboot_type, **updates):
inst = self._create_instance_obj()
inst.update(updates)
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.reboot,
self.context, inst, reboot_type)
def test_reboot_hard_active(self):
self._test_reboot_type(vm_states.ACTIVE, 'HARD')
def test_reboot_hard_error(self):
self._test_reboot_type(vm_states.ERROR, 'HARD')
def test_reboot_hard_rebooting(self):
self._test_reboot_type(vm_states.ACTIVE, 'HARD',
task_state=task_states.REBOOTING)
def test_reboot_hard_reboot_started(self):
self._test_reboot_type(vm_states.ACTIVE, 'HARD',
task_state=task_states.REBOOT_STARTED)
def test_reboot_hard_reboot_pending(self):
self._test_reboot_type(vm_states.ACTIVE, 'HARD',
task_state=task_states.REBOOT_PENDING)
def test_reboot_hard_rescued(self):
self._test_reboot_type_fails('HARD', vm_state=vm_states.RESCUED)
def test_reboot_hard_resuming(self):
self._test_reboot_type(vm_states.ACTIVE,
'HARD', task_state=task_states.RESUMING)
def test_reboot_hard_unpausing(self):
self._test_reboot_type(vm_states.ACTIVE,
'HARD', task_state=task_states.UNPAUSING)
def test_reboot_hard_suspending(self):
self._test_reboot_type(vm_states.ACTIVE,
'HARD', task_state=task_states.SUSPENDING)
def test_reboot_hard_error_not_launched(self):
self._test_reboot_type_fails('HARD', vm_state=vm_states.ERROR,
launched_at=None)
def test_reboot_soft(self):
self._test_reboot_type(vm_states.ACTIVE, 'SOFT')
def test_reboot_soft_error(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.ERROR)
def test_reboot_soft_paused(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.PAUSED)
def test_reboot_soft_stopped(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.STOPPED)
def test_reboot_soft_suspended(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.SUSPENDED)
def test_reboot_soft_rebooting(self):
self._test_reboot_type_fails('SOFT', task_state=task_states.REBOOTING)
def test_reboot_soft_rebooting_hard(self):
self._test_reboot_type_fails('SOFT',
task_state=task_states.REBOOTING_HARD)
def test_reboot_soft_reboot_started(self):
self._test_reboot_type_fails('SOFT',
task_state=task_states.REBOOT_STARTED)
def test_reboot_soft_reboot_pending(self):
self._test_reboot_type_fails('SOFT',
task_state=task_states.REBOOT_PENDING)
def test_reboot_soft_rescued(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.RESCUED)
def test_reboot_soft_error_not_launched(self):
self._test_reboot_type_fails('SOFT', vm_state=vm_states.ERROR,
launched_at=None)
def test_reboot_soft_resuming(self):
self._test_reboot_type_fails('SOFT', task_state=task_states.RESUMING)
def test_reboot_soft_pausing(self):
self._test_reboot_type_fails('SOFT', task_state=task_states.PAUSING)
def test_reboot_soft_unpausing(self):
self._test_reboot_type_fails('SOFT', task_state=task_states.UNPAUSING)
def test_reboot_soft_suspending(self):
self._test_reboot_type_fails('SOFT', task_state=task_states.SUSPENDING)
def _test_delete_resizing_part(self, inst, deltas):
old_flavor = inst.old_flavor
deltas['cores'] = -old_flavor.vcpus
deltas['ram'] = -old_flavor.memory_mb
def _set_delete_shelved_part(self, inst, mock_image_delete):
snapshot_id = inst.system_metadata.get('shelved_image_id')
if snapshot_id == SHELVED_IMAGE:
mock_image_delete.return_value = True
elif snapshot_id == SHELVED_IMAGE_NOT_FOUND:
mock_image_delete.side_effect = exception.ImageNotFound(
image_id=snapshot_id)
elif snapshot_id == SHELVED_IMAGE_NOT_AUTHORIZED:
mock_image_delete.side_effect = exception.ImageNotAuthorized(
image_id=snapshot_id)
elif snapshot_id == SHELVED_IMAGE_EXCEPTION:
mock_image_delete.side_effect = test.TestingException(
"Unexpected error")
return snapshot_id
@mock.patch.object(compute_utils,
'notify_about_instance_action')
@mock.patch.object(objects.Migration, 'get_by_instance_and_status')
@mock.patch.object(image_api.API, 'delete')
@mock.patch.object(objects.InstanceMapping, 'save')
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid')
@mock.patch.object(compute_utils,
'notify_about_instance_usage')
@mock.patch.object(db, 'instance_destroy')
@mock.patch.object(db, 'instance_system_metadata_get')
@mock.patch.object(neutron_api.API, 'deallocate_for_instance')
@mock.patch.object(db, 'instance_update_and_get_original')
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(servicegroup_api.API, 'service_is_up')
@mock.patch.object(objects.Service, 'get_by_compute_host')
@mock.patch.object(context.RequestContext, 'elevated')
@mock.patch.object(objects.BlockDeviceMappingList,
'get_by_instance_uuid', return_value=[])
@mock.patch.object(objects.Instance, 'save')
def _test_delete(self, delete_type, mock_save, mock_bdm_get, mock_elevated,
mock_get_cn, mock_up, mock_record, mock_inst_update,
mock_deallocate, mock_inst_meta, mock_inst_destroy,
mock_notify_legacy, mock_get_inst,
mock_save_im, mock_image_delete, mock_mig_get,
mock_notify, **attrs):
expected_save_calls = [mock.call()]
expected_record_calls = []
expected_elevated_calls = []
inst = self._create_instance_obj()
inst.update(attrs)
inst._context = self.context
deltas = {'instances': -1,
'cores': -inst.flavor.vcpus,
'ram': -inst.flavor.memory_mb}
delete_time = datetime.datetime(1955, 11, 5, 9, 30,
tzinfo=iso8601.UTC)
self.useFixture(utils_fixture.TimeFixture(delete_time))
task_state = (delete_type == 'soft_delete' and
task_states.SOFT_DELETING or task_states.DELETING)
updates = {'progress': 0, 'task_state': task_state}
if delete_type == 'soft_delete':
updates['deleted_at'] = delete_time
rpcapi = self.compute_api.compute_rpcapi
mock_confirm = self.useFixture(
fixtures.MockPatchObject(rpcapi, 'confirm_resize')).mock
def _reset_task_state(context, instance, migration, src_host,
cast=False):
inst.update({'task_state': None})
# After confirm resize action, instance task_state is reset to None
mock_confirm.side_effect = _reset_task_state
is_shelved = inst.vm_state in (vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED)
if is_shelved:
snapshot_id = self._set_delete_shelved_part(inst,
mock_image_delete)
mock_terminate = self.useFixture(
fixtures.MockPatchObject(rpcapi, 'terminate_instance')).mock
mock_soft_delete = self.useFixture(
fixtures.MockPatchObject(rpcapi, 'soft_delete_instance')).mock
if inst.task_state == task_states.RESIZE_FINISH:
self._test_delete_resizing_part(inst, deltas)
# NOTE(comstud): This is getting messy. But what we are wanting
# to test is:
# * Check for downed host
# * If downed host:
# * Clean up instance, destroying it, sending notifications.
# (Tested in _test_downed_host_part())
# * If not downed host:
# * Record the action start.
# * Cast to compute_rpcapi.<method>
cast = True
is_downed_host = inst.host == 'down-host' or inst.host is None
if inst.vm_state == vm_states.RESIZED:
migration = objects.Migration._from_db_object(
self.context, objects.Migration(),
test_migration.fake_db_migration())
mock_elevated.return_value = self.context
expected_elevated_calls.append(mock.call())
mock_mig_get.return_value = migration
expected_record_calls.append(
mock.call(self.context, inst,
instance_actions.CONFIRM_RESIZE))
# After confirm resize action, instance task_state
# is reset to None, so is the expected value. But
# for soft delete, task_state will be again reset
# back to soft-deleting in the code to avoid status
# checking failure.
updates['task_state'] = None
if delete_type == 'soft_delete':
expected_save_calls.append(mock.call())
updates['task_state'] = 'soft-deleting'
if inst.host is not None:
mock_elevated.return_value = self.context
expected_elevated_calls.append(mock.call())
mock_get_cn.return_value = objects.Service()
mock_up.return_value = (inst.host != 'down-host')
if is_downed_host:
mock_elevated.return_value = self.context
expected_elevated_calls.append(mock.call())
expected_save_calls.append(mock.call())
state = ('soft' in delete_type and vm_states.SOFT_DELETED or
vm_states.DELETED)
updates.update({'vm_state': state,
'task_state': None,
'terminated_at': delete_time,
'deleted_at': delete_time,
'deleted': True})
fake_inst = fake_instance.fake_db_instance(**updates)
mock_inst_destroy.return_value = fake_inst
cell = objects.CellMapping(uuid=uuids.cell,
transport_url='fake://',
database_connection='fake://')
im = objects.InstanceMapping(cell_mapping=cell)
mock_get_inst.return_value = im
cast = False
if cast:
expected_record_calls.append(mock.call(self.context, inst,
instance_actions.DELETE))
# NOTE(takashin): If objects.Instance.destroy() is called,
# objects.Instance.uuid (inst.uuid) and host (inst.host) are changed.
# So preserve them before calling the method to test.
instance_uuid = inst.uuid
instance_host = inst.host
getattr(self.compute_api, delete_type)(self.context, inst)
for k, v in updates.items():
self.assertEqual(inst[k], v)
mock_save.assert_has_calls(expected_save_calls)
mock_bdm_get.assert_called_once_with(self.context, instance_uuid)
if expected_record_calls:
mock_record.assert_has_calls(expected_record_calls)
if expected_elevated_calls:
mock_elevated.assert_has_calls(expected_elevated_calls)
if inst.vm_state == vm_states.RESIZED:
mock_mig_get.assert_called_once_with(
self.context, instance_uuid, 'finished')
mock_confirm.assert_called_once_with(
self.context, inst, migration, migration['source_compute'],
cast=False)
if instance_host is not None:
mock_get_cn.assert_called_once_with(self.context,
instance_host)
mock_up.assert_called_once_with(
test.MatchType(objects.Service))
if is_downed_host:
if 'soft' in delete_type:
mock_notify_legacy.assert_has_calls([
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.start'),
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.end')])
else:
mock_notify_legacy.assert_has_calls([
mock.call(self.compute_api.notifier, self.context,
inst, '%s.start' % delete_type),
mock.call(self.compute_api.notifier, self.context,
inst, '%s.end' % delete_type)])
mock_deallocate.assert_called_once_with(self.context, inst)
mock_inst_destroy.assert_called_once_with(
self.context, instance_uuid, constraint=None,
hard_delete=False)
mock_get_inst.assert_called_with(self.context, instance_uuid)
self.assertEqual(2, mock_get_inst.call_count)
self.assertTrue(mock_get_inst.return_value.queued_for_delete)
mock_save_im.assert_called_once_with()
if cast:
if delete_type == 'soft_delete':
mock_soft_delete.assert_called_once_with(self.context, inst)
elif delete_type in ['delete', 'force_delete']:
mock_terminate.assert_called_once_with(
self.context, inst, [])
if is_shelved:
mock_image_delete.assert_called_once_with(self.context,
snapshot_id)
if not cast and delete_type == 'delete':
mock_notify.assert_has_calls([
mock.call(self.context, inst, host='fake-mini',
source='nova-api',
action=delete_type, phase='start'),
mock.call(self.context, inst, host='fake-mini',
source='nova-api',
action=delete_type, phase='end')])
def test_delete(self):
self._test_delete('delete')
def test_delete_if_not_launched(self):
self._test_delete('delete', launched_at=None)
def test_delete_in_resizing(self):
old_flavor = objects.Flavor(vcpus=1, memory_mb=512, extra_specs={})
self._test_delete('delete',
task_state=task_states.RESIZE_FINISH,
old_flavor=old_flavor)
def test_delete_in_resized(self):
self._test_delete('delete', vm_state=vm_states.RESIZED)
def test_delete_shelved(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE}
self._test_delete('delete',
vm_state=vm_states.SHELVED,
system_metadata=fake_sys_meta)
def test_delete_shelved_offloaded(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE}
self._test_delete('delete',
vm_state=vm_states.SHELVED_OFFLOADED,
system_metadata=fake_sys_meta)
def test_delete_shelved_image_not_found(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE_NOT_FOUND}
self._test_delete('delete',
vm_state=vm_states.SHELVED_OFFLOADED,
system_metadata=fake_sys_meta)
def test_delete_shelved_image_not_authorized(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE_NOT_AUTHORIZED}
self._test_delete('delete',
vm_state=vm_states.SHELVED_OFFLOADED,
system_metadata=fake_sys_meta)
def test_delete_shelved_exception(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE_EXCEPTION}
self._test_delete('delete',
vm_state=vm_states.SHELVED,
system_metadata=fake_sys_meta)
def test_delete_with_down_host(self):
self._test_delete('delete', host='down-host')
def test_delete_soft_with_down_host(self):
self._test_delete('soft_delete', host='down-host')
def test_delete_soft_in_resized(self):
self._test_delete('soft_delete', vm_state=vm_states.RESIZED)
def test_delete_soft(self):
self._test_delete('soft_delete')
def test_delete_forced(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE}
for vm_state in self._get_vm_states():
if vm_state in (vm_states.SHELVED, vm_states.SHELVED_OFFLOADED):
self._test_delete('force_delete',
vm_state=vm_state,
system_metadata=fake_sys_meta)
self._test_delete('force_delete', vm_state=vm_state)
@mock.patch('nova.compute.api.API._delete_while_booting',
return_value=False)
@mock.patch('nova.compute.api.API._lookup_instance')
@mock.patch('nova.objects.BlockDeviceMappingList.get_by_instance_uuid')
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.compute.utils.notify_about_instance_usage')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch('nova.compute.api.API._local_delete')
def test_delete_error_state_with_no_host(
self, mock_local_delete, mock_service_get, _mock_notify,
_mock_save, mock_bdm_get, mock_lookup, _mock_del_booting):
# Instance in error state with no host should be a local delete
# for non API cells
inst = self._create_instance_obj(params=dict(vm_state=vm_states.ERROR,
host=None))
mock_lookup.return_value = None, inst
with mock.patch.object(self.compute_api.compute_rpcapi,
'terminate_instance') as mock_terminate:
self.compute_api.delete(self.context, inst)
mock_local_delete.assert_called_once_with(
self.context, inst, mock_bdm_get.return_value,
'delete', self.compute_api._do_delete)
mock_terminate.assert_not_called()
mock_service_get.assert_not_called()
@mock.patch('nova.compute.api.API._delete_while_booting',
return_value=False)
@mock.patch('nova.compute.api.API._lookup_instance')
@mock.patch('nova.objects.BlockDeviceMappingList.get_by_instance_uuid')
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.compute.utils.notify_about_instance_usage')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch('nova.context.RequestContext.elevated')
@mock.patch('nova.servicegroup.api.API.service_is_up', return_value=True)
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch('nova.compute.api.API._local_delete')
def test_delete_error_state_with_host_set(
self, mock_local_delete, _mock_record, mock_service_up,
mock_elevated, mock_service_get, _mock_notify, _mock_save,
mock_bdm_get, mock_lookup, _mock_del_booting):
# Instance in error state with host set should be a non-local delete
# for non API cells if the service is up
inst = self._create_instance_obj(params=dict(vm_state=vm_states.ERROR,
host='fake-host'))
mock_lookup.return_value = inst
with mock.patch.object(self.compute_api.compute_rpcapi,
'terminate_instance') as mock_terminate:
self.compute_api.delete(self.context, inst)
mock_service_get.assert_called_once_with(
mock_elevated.return_value, 'fake-host')
mock_service_up.assert_called_once_with(
mock_service_get.return_value)
mock_terminate.assert_called_once_with(
self.context, inst, mock_bdm_get.return_value)
mock_local_delete.assert_not_called()
def test_delete_forced_when_task_state_is_not_none(self):
for vm_state in self._get_vm_states():
self._test_delete('force_delete', vm_state=vm_state,
task_state=task_states.RESIZE_MIGRATING)
@mock.patch.object(compute_utils, 'notify_about_instance_action')
@mock.patch.object(compute_utils, 'notify_about_instance_usage')
@mock.patch.object(db, 'instance_destroy')
@mock.patch.object(db, 'constraint')
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid')
def test_delete_fast_if_host_not_set(self, mock_br_get, mock_save,
mock_bdm_get, mock_cons,
mock_inst_destroy,
mock_notify_legacy, mock_notify):
self.useFixture(nova_fixtures.AllServicesCurrent())
inst = self._create_instance_obj()
inst.host = ''
updates = {'progress': 0, 'task_state': task_states.DELETING}
mock_lookup = self.useFixture(
fixtures.MockPatchObject(self.compute_api,
'_lookup_instance')).mock
mock_lookup.return_value = (None, inst)
mock_bdm_get.return_value = objects.BlockDeviceMappingList()
mock_br_get.side_effect = exception.BuildRequestNotFound(
uuid=inst.uuid)
mock_cons.return_value = 'constraint'
delete_time = datetime.datetime(1955, 11, 5, 9, 30,
tzinfo=iso8601.UTC)
updates['deleted_at'] = delete_time
updates['deleted'] = True
fake_inst = fake_instance.fake_db_instance(**updates)
mock_inst_destroy.return_value = fake_inst
instance_uuid = inst.uuid
self.compute_api.delete(self.context, inst)
for k, v in updates.items():
self.assertEqual(inst[k], v)
mock_lookup.assert_called_once_with(self.context, instance_uuid)
mock_bdm_get.assert_called_once_with(self.context, instance_uuid)
mock_br_get.assert_called_once_with(self.context, instance_uuid)
mock_save.assert_called_once_with()
mock_notify_legacy.assert_has_calls([
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.start'),
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.end')])
mock_notify.assert_has_calls([
mock.call(self.context, inst, host='fake-mini',
source='nova-api', action='delete', phase='start'),
mock.call(self.context, inst, host='fake-mini',
source='nova-api', action='delete', phase='end')])
mock_cons.assert_called_once_with(host=mock.ANY)
mock_inst_destroy.assert_called_once_with(
self.context, instance_uuid, constraint='constraint',
hard_delete=False)
def _fake_do_delete(context, instance, bdms,
rservations=None, local=False):
pass
@mock.patch.object(compute_utils, 'notify_about_instance_action')
@mock.patch.object(objects.BlockDeviceMapping, 'destroy')
@mock.patch.object(cinder.API, 'detach')
@mock.patch.object(compute_utils, 'notify_about_instance_usage')
@mock.patch.object(neutron_api.API, 'deallocate_for_instance')
@mock.patch.object(context.RequestContext, 'elevated')
@mock.patch.object(objects.Instance, 'destroy')
def test_local_delete_with_deleted_volume(
self, mock_inst_destroy, mock_elevated, mock_dealloc,
mock_notify_legacy, mock_detach, mock_bdm_destroy, mock_notify):
bdms = [objects.BlockDeviceMapping(
**fake_block_device.FakeDbBlockDeviceDict(
{'id': 42, 'volume_id': 'volume_id',
'source_type': 'volume', 'destination_type': 'volume',
'delete_on_termination': False}))]
inst = self._create_instance_obj()
inst._context = self.context
mock_elevated.return_value = self.context
mock_detach.side_effect = exception.VolumeNotFound('volume_id')
self.compute_api._local_delete(self.context, inst, bdms,
'delete',
self._fake_do_delete)
mock_notify_legacy.assert_has_calls([
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.start'),
mock.call(self.compute_api.notifier, self.context,
inst, 'delete.end')])
mock_notify.assert_has_calls([
mock.call(self.context, inst, host='fake-mini', source='nova-api',
action='delete', phase='start'),
mock.call(self.context, inst, host='fake-mini', source='nova-api',
action='delete', phase='end')])
mock_elevated.assert_has_calls([mock.call(), mock.call()])
mock_detach.assert_called_once_with(mock.ANY, 'volume_id', inst.uuid)
mock_bdm_destroy.assert_called_once_with()
mock_inst_destroy.assert_called_once_with()
mock_dealloc.assert_called_once_with(self.context, inst)
@mock.patch.object(objects.BlockDeviceMapping, 'destroy')
def test_local_cleanup_bdm_volumes_stashed_connector(self, mock_destroy):
"""Tests that we call volume_api.terminate_connection when we found
a stashed connector in the bdm.connection_info dict.
"""
inst = self._create_instance_obj()
# create two fake bdms, one is a volume and one isn't, both will be
# destroyed but we only cleanup the volume bdm in cinder
conn_info = {'connector': {'host': inst.host}}
vol_bdm = objects.BlockDeviceMapping(self.context, id=1,
instance_uuid=inst.uuid,
volume_id=uuids.volume_id,
source_type='volume',
destination_type='volume',
delete_on_termination=True,
connection_info=jsonutils.dumps(
conn_info
),
attachment_id=None,)
loc_bdm = objects.BlockDeviceMapping(self.context, id=2,
instance_uuid=inst.uuid,
volume_id=uuids.volume_id2,
source_type='blank',
destination_type='local')
bdms = objects.BlockDeviceMappingList(objects=[vol_bdm, loc_bdm])
@mock.patch.object(self.compute_api.volume_api, 'terminate_connection')
@mock.patch.object(self.compute_api.volume_api, 'detach')
@mock.patch.object(self.compute_api.volume_api, 'delete')
@mock.patch.object(self.context, 'elevated', return_value=self.context)
def do_test(self, mock_elevated, mock_delete,
mock_detach, mock_terminate):
self.compute_api._local_cleanup_bdm_volumes(
bdms, inst, self.context)
mock_terminate.assert_called_once_with(
self.context, uuids.volume_id, conn_info['connector'])
mock_detach.assert_called_once_with(
self.context, uuids.volume_id, inst.uuid)
mock_delete.assert_called_once_with(self.context, uuids.volume_id)
self.assertEqual(2, mock_destroy.call_count)
do_test(self)
@mock.patch.object(objects.BlockDeviceMapping, 'destroy')
def test_local_cleanup_bdm_volumes_with_attach_id(self, mock_destroy):
"""Tests that we call volume_api.attachment_delete when we have an
attachment_id in the bdm.
"""
instance = self._create_instance_obj()
conn_info = {'connector': {'host': instance.host}}
vol_bdm = objects.BlockDeviceMapping(
self.context,
id=1,
instance_uuid=instance.uuid,
volume_id=uuids.volume_id,
source_type='volume',
destination_type='volume',
delete_on_termination=True,
connection_info=jsonutils.dumps(conn_info),
attachment_id=uuids.attachment_id)
bdms = objects.BlockDeviceMappingList(objects=[vol_bdm])
@mock.patch.object(self.compute_api.volume_api, 'delete')
@mock.patch.object(self.compute_api.volume_api, 'attachment_delete')
@mock.patch.object(self.context, 'elevated', return_value=self.context)
def do_test(self, mock_elevated, mock_attach_delete, mock_delete):
self.compute_api._local_cleanup_bdm_volumes(
bdms, instance, self.context)
mock_attach_delete.assert_called_once_with(
self.context, vol_bdm.attachment_id)
mock_delete.assert_called_once_with(
self.context, vol_bdm.volume_id)
mock_destroy.assert_called_once_with()
do_test(self)
@mock.patch.object(objects.BlockDeviceMapping, 'destroy')
def test_local_cleanup_bdm_volumes_stashed_connector_host_none(
self, mock_destroy):
"""Tests that we call volume_api.terminate_connection when we found
a stashed connector in the bdm.connection_info dict.
This tests the case where:
1) the instance host is None
2) the instance vm_state is one where we expect host to be None
We allow a mismatch of the host in this situation if the instance is
in a state where we expect its host to have been set to None, such
as ERROR or SHELVED_OFFLOADED.
"""
params = dict(host=None, vm_state=vm_states.ERROR)
inst = self._create_instance_obj(params=params)
conn_info = {'connector': {'host': 'orig-host'}}
vol_bdm = objects.BlockDeviceMapping(self.context, id=1,
instance_uuid=inst.uuid,
volume_id=uuids.volume_id,
source_type='volume',
destination_type='volume',
delete_on_termination=True,
connection_info=jsonutils.dumps(
conn_info),
attachment_id=None)
bdms = objects.BlockDeviceMappingList(objects=[vol_bdm])
@mock.patch.object(self.compute_api.volume_api, 'terminate_connection')
@mock.patch.object(self.compute_api.volume_api, 'detach')
@mock.patch.object(self.compute_api.volume_api, 'delete')
@mock.patch.object(self.context, 'elevated', return_value=self.context)
def do_test(self, mock_elevated, mock_delete,
mock_detach, mock_terminate):
self.compute_api._local_cleanup_bdm_volumes(
bdms, inst, self.context)
mock_terminate.assert_called_once_with(
self.context, uuids.volume_id, conn_info['connector'])
mock_detach.assert_called_once_with(
self.context, uuids.volume_id, inst.uuid)
mock_delete.assert_called_once_with(self.context, uuids.volume_id)
mock_destroy.assert_called_once_with()
do_test(self)
def test_delete_disabled(self):
# If 'disable_terminate' is True, log output is executed only and
# just return immediately.
inst = self._create_instance_obj()
inst.disable_terminate = True
self.compute_api.delete(self.context, inst)
@mock.patch.object(objects.Instance, 'save',
side_effect=test.TestingException)
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid',
return_value=objects.BlockDeviceMappingList())
def test_delete_soft_rollback(self, mock_get, mock_save):
inst = self._create_instance_obj()
delete_time = datetime.datetime(1955, 11, 5)
self.useFixture(utils_fixture.TimeFixture(delete_time))
self.assertRaises(test.TestingException,
self.compute_api.soft_delete, self.context, inst)
mock_get.assert_called_once_with(self.context, inst.uuid)
mock_save.assert_called_once_with()
@mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid')
def test_attempt_delete_of_buildrequest_success(self, mock_get_by_inst):
build_req_mock = mock.MagicMock()
mock_get_by_inst.return_value = build_req_mock
inst = self._create_instance_obj()
self.assertTrue(
self.compute_api._attempt_delete_of_buildrequest(self.context,
inst))
self.assertTrue(build_req_mock.destroy.called)
@mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid')
def test_attempt_delete_of_buildrequest_not_found(self, mock_get_by_inst):
mock_get_by_inst.side_effect = exception.BuildRequestNotFound(
uuid='fake')
inst = self._create_instance_obj()
self.assertFalse(
self.compute_api._attempt_delete_of_buildrequest(self.context,
inst))
def test_attempt_delete_of_buildrequest_already_deleted(self):
inst = self._create_instance_obj()
build_req_mock = mock.MagicMock()
build_req_mock.destroy.side_effect = exception.BuildRequestNotFound(
uuid='fake')
with mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid',
return_value=build_req_mock):
self.assertFalse(
self.compute_api._attempt_delete_of_buildrequest(self.context,
inst))
self.assertTrue(build_req_mock.destroy.called)
def test_delete_while_booting_buildreq_not_deleted(self):
self.useFixture(nova_fixtures.AllServicesCurrent())
inst = self._create_instance_obj()
with mock.patch.object(self.compute_api,
'_attempt_delete_of_buildrequest',
return_value=False):
self.assertFalse(
self.compute_api._delete_while_booting(self.context, inst))
def test_delete_while_booting_buildreq_deleted_instance_none(self):
self.useFixture(nova_fixtures.AllServicesCurrent())
inst = self._create_instance_obj()
@mock.patch.object(self.compute_api, '_attempt_delete_of_buildrequest',
return_value=True)
@mock.patch.object(self.compute_api, '_lookup_instance',
return_value=(None, None))
def test(mock_lookup, mock_attempt):
self.assertTrue(
self.compute_api._delete_while_booting(self.context,
inst))
test()
def test_delete_while_booting_buildreq_deleted_instance_not_found(self):
self.useFixture(nova_fixtures.AllServicesCurrent())
inst = self._create_instance_obj()
@mock.patch.object(self.compute_api, '_attempt_delete_of_buildrequest',
return_value=True)
@mock.patch.object(self.compute_api, '_lookup_instance',
side_effect=exception.InstanceNotFound(
instance_id='fake'))
def test(mock_lookup, mock_attempt):
self.assertTrue(
self.compute_api._delete_while_booting(self.context,
inst))
test()
@mock.patch('nova.compute.utils.notify_about_instance_delete')
@mock.patch('nova.objects.Instance.destroy')
def test_delete_instance_from_cell0(self, destroy_mock, notify_mock):
"""Tests the case that the instance does not have a host and was not
deleted while building, so conductor put it into cell0 so the API has
to delete the instance from cell0.
"""
instance = self._create_instance_obj({'host': None})
cell0 = objects.CellMapping(uuid=objects.CellMapping.CELL0_UUID)
with test.nested(
mock.patch.object(self.compute_api, '_delete_while_booting',
return_value=False),
mock.patch.object(self.compute_api, '_lookup_instance',
return_value=(cell0, instance)),
) as (
_delete_while_booting, _lookup_instance,
):
self.compute_api._delete(
self.context, instance, 'delete', mock.NonCallableMock())
_delete_while_booting.assert_called_once_with(
self.context, instance)
_lookup_instance.assert_called_once_with(
self.context, instance.uuid)
notify_mock.assert_called_once_with(
self.compute_api.notifier, self.context, instance)
destroy_mock.assert_called_once_with()
@mock.patch.object(context, 'target_cell')
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
side_effect=exception.InstanceMappingNotFound(
uuid='fake'))
def test_lookup_instance_mapping_none(self, mock_map_get,
mock_target_cell):
instance = self._create_instance_obj()
with mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance) as mock_inst_get:
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)
@mock.patch.object(context, 'target_cell')
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
return_value=objects.InstanceMapping(cell_mapping=None))
def test_lookup_instance_cell_mapping_none(self, mock_map_get,
mock_target_cell):
instance = self._create_instance_obj()
with mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance) as mock_inst_get:
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)
@mock.patch.object(context, 'target_cell')
def test_lookup_instance_cell_mapping(self, mock_target_cell):
instance = self._create_instance_obj()
mock_target_cell.return_value.__enter__.return_value = self.context
inst_map = objects.InstanceMapping(
cell_mapping=objects.CellMapping(database_connection='',
transport_url='none'))
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
return_value=inst_map)
@mock.patch.object(objects.Instance, 'get_by_uuid',
return_value=instance)
def test(mock_inst_get, mock_map_get):
cell, ret_instance = self.compute_api._lookup_instance(
self.context, instance.uuid)
expected_cell = inst_map.cell_mapping
self.assertEqual((expected_cell, instance),
(cell, ret_instance))
mock_inst_get.assert_called_once_with(self.context, instance.uuid)
mock_target_cell.assert_called_once_with(self.context,
inst_map.cell_mapping)
test()
@mock.patch.object(objects.Migration, 'save')
@mock.patch.object(objects.Migration, 'get_by_instance_and_status')
@mock.patch.object(context.RequestContext, 'elevated')
def _test_confirm_resize(self, mock_elevated, mock_get, mock_save,
mig_ref_passed=False):
params = dict(vm_state=vm_states.RESIZED)
fake_inst = self._create_instance_obj(params=params)
fake_mig = objects.Migration._from_db_object(
self.context, objects.Migration(),
test_migration.fake_db_migration())
mock_record = self.useFixture(
fixtures.MockPatchObject(self.compute_api,
'_record_action_start')).mock
mock_confirm = self.useFixture(
fixtures.MockPatchObject(self.compute_api.compute_rpcapi,
'confirm_resize')).mock
mock_elevated.return_value = self.context
if not mig_ref_passed:
mock_get.return_value = fake_mig
def _check_mig(expected_task_state=None):
self.assertEqual('confirming', fake_mig.status)
mock_save.side_effect = _check_mig
if mig_ref_passed:
self.compute_api.confirm_resize(self.context, fake_inst,
migration=fake_mig)
else:
self.compute_api.confirm_resize(self.context, fake_inst)
mock_elevated.assert_called_once_with()
mock_save.assert_called_once_with()
mock_record.assert_called_once_with(self.context, fake_inst,
'confirmResize')
mock_confirm.assert_called_once_with(self.context, fake_inst, fake_mig,
'compute-source')
if not mig_ref_passed:
mock_get.assert_called_once_with(self.context, fake_inst['uuid'],
'finished')
def test_confirm_resize(self):
self._test_confirm_resize()
def test_confirm_resize_with_migration_ref(self):
self._test_confirm_resize(mig_ref_passed=True)
@mock.patch('nova.network.neutronv2.api.API.'
'get_requested_resource_for_instance',
return_value=mock.sentinel.res_req)
@mock.patch('nova.availability_zones.get_host_availability_zone',
return_value='nova')
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.objects.Migration.get_by_instance_and_status')
@mock.patch('nova.context.RequestContext.elevated')
@mock.patch('nova.objects.RequestSpec.get_by_instance_uuid')
def _test_revert_resize(
self, mock_get_reqspec, mock_elevated, mock_get_migration,
mock_check, mock_get_host_az, mock_get_requested_resources):
params = dict(vm_state=vm_states.RESIZED)
fake_inst = self._create_instance_obj(params=params)
fake_inst.info_cache.network_info = model.NetworkInfo([
model.VIF(id=uuids.port1, profile={'allocation': uuids.rp})])
fake_inst.old_flavor = fake_inst.flavor
fake_mig = objects.Migration._from_db_object(
self.context, objects.Migration(),
test_migration.fake_db_migration())
mock_elevated.return_value = self.context
mock_get_migration.return_value = fake_mig
def _check_state(expected_task_state=None):
self.assertEqual(task_states.RESIZE_REVERTING,
fake_inst.task_state)
def _check_mig(expected_task_state=None):
self.assertEqual('reverting', fake_mig.status)
with test.nested(
mock.patch.object(fake_inst, 'save', side_effect=_check_state),
mock.patch.object(fake_mig, 'save', side_effect=_check_mig),
mock.patch.object(self.compute_api, '_record_action_start'),
mock.patch.object(self.compute_api.compute_rpcapi, 'revert_resize')
) as (mock_inst_save, mock_mig_save, mock_record_action,
mock_revert_resize):
self.compute_api.revert_resize(self.context, fake_inst)
mock_elevated.assert_called_once_with()
mock_get_migration.assert_called_once_with(
self.context, fake_inst['uuid'], 'finished')
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_mig_save.assert_called_once_with()
mock_get_reqspec.assert_called_once_with(
self.context, fake_inst.uuid)
mock_get_reqspec.return_value.save.assert_called_once_with()
mock_record_action.assert_called_once_with(self.context, fake_inst,
'revertResize')
mock_revert_resize.assert_called_once_with(
self.context, fake_inst, fake_mig, 'compute-dest',
mock_get_reqspec.return_value)
mock_get_requested_resources.assert_called_once_with(
self.context, fake_inst.uuid)
self.assertEqual(
mock.sentinel.res_req,
mock_get_reqspec.return_value.requested_resources)
def test_revert_resize(self):
self._test_revert_resize()
@mock.patch('nova.network.neutronv2.api.API.'
'get_requested_resource_for_instance')
@mock.patch('nova.availability_zones.get_host_availability_zone',
return_value='nova')
@mock.patch('nova.objects.Quotas.check_deltas')
@mock.patch('nova.objects.Migration.get_by_instance_and_status')
@mock.patch('nova.context.RequestContext.elevated')
@mock.patch('nova.objects.RequestSpec')
def test_revert_resize_concurrent_fail(
self, mock_reqspec, mock_elevated, mock_get_migration, mock_check,
mock_get_host_az, mock_get_requested_resources):
params = dict(vm_state=vm_states.RESIZED)
fake_inst = self._create_instance_obj(params=params)
fake_inst.info_cache.network_info = model.NetworkInfo([])
fake_inst.old_flavor = fake_inst.flavor
fake_mig = objects.Migration._from_db_object(
self.context, objects.Migration(),
test_migration.fake_db_migration())
mock_elevated.return_value = self.context
mock_get_migration.return_value = fake_mig
exc = exception.UnexpectedTaskStateError(
instance_uuid=fake_inst['uuid'],
actual={'task_state': task_states.RESIZE_REVERTING},
expected={'task_state': [None]})
with mock.patch.object(
fake_inst, 'save', side_effect=exc) as mock_inst_save:
self.assertRaises(exception.UnexpectedTaskStateError,
self.compute_api.revert_resize,
self.context,
fake_inst)
mock_elevated.assert_called_once_with()
mock_get_migration.assert_called_once_with(
self.context, fake_inst['uuid'], 'finished')
mock_inst_save.assert_called_once_with(expected_task_state=[None])
mock_get_requested_resources.assert_not_called()
@mock.patch('nova.compute.utils.is_volume_backed_instance',
return_value=False)
@mock.patch('nova.compute.api.API._validate_flavor_image_nostatus')
@mock.patch('nova.objects.Migration')
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(quotas_obj.Quotas, 'limit_check_project_and_user')
@mock.patch.object(quotas_obj.Quotas, 'count_as_dict')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(compute_utils, 'upsize_quota_delta')
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
def _test_resize(self, mock_get_all_by_host,
mock_get_by_instance_uuid, mock_get_flavor, mock_upsize,
mock_inst_save, mock_count, mock_limit, mock_record,
mock_migration, mock_validate, mock_is_vol_backed,
flavor_id_passed=True,
same_host=False, allow_same_host=False,
project_id=None,
same_flavor=False,
clean_shutdown=True,
host_name=None,
request_spec=True,
requested_destination=False,
allow_cross_cell_resize=False):
self.flags(allow_resize_to_same_host=allow_same_host)
params = {}
if project_id is not None:
# To test instance w/ different project id than context (admin)
params['project_id'] = project_id
fake_inst = self._create_instance_obj(params=params)
mock_resize = self.useFixture(
fixtures.MockPatchObject(self.compute_api.compute_task_api,
'resize_instance')).mock
if host_name:
mock_get_all_by_host.return_value = [objects.ComputeNode(
host=host_name, hypervisor_hostname='hypervisor_host')]
current_flavor = fake_inst.get_flavor()
if flavor_id_passed:
new_flavor = self._create_flavor(id=200, flavorid='new-flavor-id',
name='new_flavor', disabled=False)
if same_flavor:
new_flavor.id = current_flavor.id
mock_get_flavor.return_value = new_flavor
else:
new_flavor = current_flavor
if not (flavor_id_passed and same_flavor):
project_id, user_id = quotas_obj.ids_from_instance(self.context,
fake_inst)
if flavor_id_passed:
mock_upsize.return_value = {'cores': 0, 'ram': 0}
proj_count = {'instances': 1, 'cores': current_flavor.vcpus,
'ram': current_flavor.memory_mb}
user_count = proj_count.copy()
mock_count.return_value = {'project': proj_count,
'user': user_count}
def _check_state(expected_task_state=None):
self.assertEqual(task_states.RESIZE_PREP,
fake_inst.task_state)
self.assertEqual(fake_inst.progress, 0)
mock_inst_save.side_effect = _check_state
if allow_same_host:
filter_properties = {'ignore_hosts': []}
else:
filter_properties = {'ignore_hosts': [fake_inst['host']]}
if request_spec:
fake_spec = objects.RequestSpec()
if requested_destination:
cell1 = objects.CellMapping(uuid=uuids.cell1, name='cell1')
fake_spec.requested_destination = objects.Destination(
host='dummy_host', node='dummy_node', cell=cell1)
mock_get_by_instance_uuid.return_value = fake_spec
else:
mock_get_by_instance_uuid.side_effect = (
exception.RequestSpecNotFound(instance_uuid=fake_inst.id))
fake_spec = None
scheduler_hint = {'filter_properties': filter_properties}
mock_allow_cross_cell_resize = self.useFixture(
fixtures.MockPatchObject(
self.compute_api, '_allow_cross_cell_resize')).mock
mock_allow_cross_cell_resize.return_value = allow_cross_cell_resize
if flavor_id_passed:
self.compute_api.resize(self.context, fake_inst,
flavor_id='new-flavor-id',
clean_shutdown=clean_shutdown,
host_name=host_name)
else:
if request_spec:
self.compute_api.resize(self.context, fake_inst,
clean_shutdown=clean_shutdown,
host_name=host_name)
else:
self.assertRaises(exception.RequestSpecNotFound,
self.compute_api.resize,
self.context, fake_inst,
clean_shutdown=clean_shutdown,
host_name=host_name)
if request_spec:
if allow_same_host:
self.assertEqual([], fake_spec.ignore_hosts)
else:
self.assertEqual([fake_inst['host']], fake_spec.ignore_hosts)
if host_name is None:
self.assertIsNotNone(fake_spec.requested_destination)
else:
self.assertIn('host', fake_spec.requested_destination)
self.assertEqual(host_name,
fake_spec.requested_destination.host)
self.assertIn('node', fake_spec.requested_destination)
self.assertEqual('hypervisor_host',
fake_spec.requested_destination.node)
self.assertEqual(
allow_cross_cell_resize,
fake_spec.requested_destination.allow_cross_cell_move)
if host_name:
mock_get_all_by_host.assert_called_once_with(
self.context, host_name, True)
if flavor_id_passed:
mock_get_flavor.assert_called_once_with('new-flavor-id',
read_deleted='no')
if not (flavor_id_passed and same_flavor):
if flavor_id_passed:
mock_upsize.assert_called_once_with(
test.MatchType(objects.Flavor),
test.MatchType(objects.Flavor))
image_meta = utils.get_image_from_system_metadata(
fake_inst.system_metadata)
if not same_flavor:
mock_validate.assert_called_once_with(
self.context, image_meta, new_flavor, root_bdm=None,
validate_pci=True)
# mock.ANY might be 'instances', 'cores', or 'ram'
# depending on how the deltas dict is iterated in check_deltas
mock_count.assert_called_once_with(
self.context, mock.ANY, project_id, user_id=user_id)
# The current and new flavor have the same cores/ram
req_cores = current_flavor.vcpus
req_ram = current_flavor.memory_mb
values = {'cores': req_cores, 'ram': req_ram}
mock_limit.assert_called_once_with(
self.context, user_values=values, project_values=values,
project_id=project_id, user_id=user_id)
mock_inst_save.assert_called_once_with(
expected_task_state=[None])
else:
# This is a migration
mock_validate.assert_not_called()
mock_migration.assert_not_called()
mock_get_by_instance_uuid.assert_called_once_with(self.context,
fake_inst.uuid)
if flavor_id_passed:
mock_record.assert_called_once_with(self.context, fake_inst,
'resize')
else:
if request_spec:
mock_record.assert_called_once_with(
self.context, fake_inst, 'migrate')
else:
mock_record.assert_not_called()
if request_spec:
mock_resize.assert_called_once_with(
self.context, fake_inst,
scheduler_hint=scheduler_hint,
flavor=test.MatchType(objects.Flavor),
clean_shutdown=clean_shutdown,
request_spec=fake_spec, do_cast=True)
else:
mock_resize.assert_not_called()
def _test_migrate(self, *args, **kwargs):
self._test_resize(*args, flavor_id_passed=False, **kwargs)
def test_resize(self):
self._test_resize()
def test_resize_same_host_and_allowed(self):
self._test_resize(same_host=True, allow_same_host=True)
def test_resize_same_host_and_not_allowed(self):
self._test_resize(same_host=True, allow_same_host=False)
def test_resize_different_project_id(self):
self._test_resize(project_id='different')
def test_resize_forced_shutdown(self):
self._test_resize(clean_shutdown=False)
def test_resize_allow_cross_cell_resize_true(self):
self._test_resize(allow_cross_cell_resize=True)
@mock.patch('nova.compute.flavors.get_flavor_by_flavor_id')
@mock.patch('nova.objects.Quotas.count_as_dict')
@mock.patch('nova.objects.Quotas.limit_check_project_and_user')
def test_resize_quota_check(self, mock_check, mock_count, mock_get):
self.flags(cores=1, group='quota')
self.flags(ram=2048, group='quota')
proj_count = {'instances': 1, 'cores': 1, 'ram': 1024}
user_count = proj_count.copy()
mock_count.return_value = {'project': proj_count,
'user': user_count}
cur_flavor = objects.Flavor(id=1, name='foo', vcpus=1, memory_mb=512,
root_gb=10, disabled=False)
fake_inst = self._create_instance_obj()
fake_inst.flavor = cur_flavor
new_flavor = objects.Flavor(id=2, name='bar', vcpus=1, memory_mb=2048,
root_gb=10, disabled=False)
mock_get.return_value = new_flavor
mock_check.side_effect = exception.OverQuota(
overs=['ram'], quotas={'cores': 1, 'ram': 2048},
usages={'instances': 1, 'cores': 1, 'ram': 2048},
headroom={'ram': 2048})
self.assertRaises(exception.TooManyInstances, self.compute_api.resize,
self.context, fake_inst, flavor_id='new')
mock_check.assert_called_once_with(
self.context,
user_values={'cores': 1, 'ram': 2560},
project_values={'cores': 1, 'ram': 2560},
project_id=fake_inst.project_id, user_id=fake_inst.user_id)
def test_migrate(self):
self._test_migrate()
def test_migrate_same_host_and_allowed(self):
self._test_migrate(same_host=True, allow_same_host=True)
def test_migrate_same_host_and_not_allowed(self):
self._test_migrate(same_host=True, allow_same_host=False)
def test_migrate_different_project_id(self):
self._test_migrate(project_id='different')
def test_migrate_request_spec_not_found(self):
self._test_migrate(request_spec=False)
def test_migrate_with_requested_destination(self):
# RequestSpec has requested_destination
self._test_migrate(requested_destination=True)
def test_migrate_with_host_name(self):
self._test_migrate(host_name='target_host')
def test_migrate_with_host_name_allow_cross_cell_resize_true(self):
self._test_migrate(host_name='target_host',
allow_cross_cell_resize=True)
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host',
side_effect=exception.ComputeHostNotFound(
host='nonexistent_host'))
def test_migrate_nonexistent_host(self, mock_get_all_by_host):
fake_inst = self._create_instance_obj()
self.assertRaises(exception.ComputeHostNotFound,
self.compute_api.resize, self.context,
fake_inst, host_name='nonexistent_host')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(quotas_obj.Quotas, 'limit_check_project_and_user')
@mock.patch.object(quotas_obj.Quotas, 'count_as_dict')
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
def test_resize_invalid_flavor_fails(self, mock_get_flavor, mock_count,
mock_limit, mock_record, mock_save):
mock_resize = self.useFixture(fixtures.MockPatchObject(
self.compute_api.compute_task_api, 'resize_instance')).mock
fake_inst = self._create_instance_obj()
exc = exception.FlavorNotFound(flavor_id='flavor-id')
mock_get_flavor.side_effect = exc
self.assertRaises(exception.FlavorNotFound,
self.compute_api.resize, self.context,
fake_inst, flavor_id='flavor-id')
mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no')
# Should never reach these.
mock_count.assert_not_called()
mock_limit.assert_not_called()
mock_record.assert_not_called()
mock_resize.assert_not_called()
mock_save.assert_not_called()
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(quotas_obj.Quotas, 'limit_check_project_and_user')
@mock.patch.object(quotas_obj.Quotas, 'count_as_dict')
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
def test_resize_disabled_flavor_fails(self, mock_get_flavor, mock_count,
mock_limit, mock_record, mock_save):
mock_resize = self.useFixture(fixtures.MockPatchObject(
self.compute_api.compute_task_api, 'resize_instance')).mock
fake_inst = self._create_instance_obj()
fake_flavor = self._create_flavor(id=200, flavorid='flavor-id',
name='foo', disabled=True)
mock_get_flavor.return_value = fake_flavor
self.assertRaises(exception.FlavorNotFound,
self.compute_api.resize, self.context,
fake_inst, flavor_id='flavor-id')
mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no')
# Should never reach these.
mock_count.assert_not_called()
mock_limit.assert_not_called()
mock_record.assert_not_called()
mock_resize.assert_not_called()
mock_save.assert_not_called()
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
def test_resize_to_zero_disk_flavor_fails(self, get_flavor_by_flavor_id):
fake_inst = self._create_instance_obj()
fake_flavor = self._create_flavor(id=200, flavorid='flavor-id',
name='foo', root_gb=0)
get_flavor_by_flavor_id.return_value = fake_flavor
with mock.patch.object(compute_utils, 'is_volume_backed_instance',
return_value=False):
self.assertRaises(exception.CannotResizeDisk,
self.compute_api.resize, self.context,
fake_inst, flavor_id='flavor-id')
@mock.patch('nova.compute.api.API._validate_flavor_image_nostatus')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch('nova.conductor.conductor_api.ComputeTaskAPI.resize_instance')
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
def test_resize_to_zero_disk_flavor_volume_backed(self,
get_flavor_by_flavor_id,
resize_instance_mock,
record_mock,
get_by_inst,
validate_mock):
params = dict(image_ref='')
fake_inst = self._create_instance_obj(params=params)
fake_flavor = self._create_flavor(id=200, flavorid='flavor-id',
name='foo', root_gb=0)
get_flavor_by_flavor_id.return_value = fake_flavor
@mock.patch.object(compute_utils, 'is_volume_backed_instance',
return_value=True)
@mock.patch.object(fake_inst, 'save')
def do_test(mock_save, mock_volume):
self.compute_api.resize(self.context, fake_inst,
flavor_id='flavor-id')
mock_volume.assert_called_once_with(self.context, fake_inst)
do_test()
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(compute_api.API, '_record_action_start')
@mock.patch.object(quotas_obj.Quotas,
'limit_check_project_and_user')
@mock.patch.object(quotas_obj.Quotas, 'count_as_dict')
@mock.patch.object(compute_utils, 'upsize_quota_delta')
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
def test_resize_quota_exceeds_fails(self, mock_get_flavor, mock_upsize,
mock_count, mock_limit, mock_record,
mock_save):
mock_resize = self.useFixture(fixtures.MockPatchObject(
self.compute_api.compute_task_api, 'resize_instance')).mock
fake_inst = self._create_instance_obj()
fake_flavor = self._create_flavor(id=200, flavorid='flavor-id',
name='foo', disabled=False)
mock_get_flavor.return_value = fake_flavor
deltas = dict(cores=0)
mock_upsize.return_value = deltas
quotas = {'cores': 0}
overs = ['cores']
over_quota_args = dict(quotas=quotas,
usages={'instances': 1, 'cores': 1, 'ram': 512},
overs=overs)
proj_count = {'instances': 1, 'cores': fake_inst.flavor.vcpus,
'ram': fake_inst.flavor.memory_mb}
user_count = proj_count.copy()
mock_count.return_value = {'project': proj_count, 'user': user_count}
req_cores = fake_inst.flavor.vcpus
req_ram = fake_inst.flavor.memory_mb
values = {'cores': req_cores, 'ram': req_ram}
mock_limit.side_effect = exception.OverQuota(**over_quota_args)
self.assertRaises(exception.TooManyInstances,
self.compute_api.resize, self.context,
fake_inst, flavor_id='flavor-id')
mock_save.assert_not_called()
mock_get_flavor.assert_called_once_with('flavor-id', read_deleted='no')
mock_upsize.assert_called_once_with(test.MatchType(objects.Flavor),
test.MatchType(objects.Flavor))
# mock.ANY might be 'instances', 'cores', or 'ram'
# depending on how the deltas dict is iterated in check_deltas
mock_count.assert_called_once_with(self.context, mock.ANY,
fake_inst.project_id,
user_id=fake_inst.user_id)
mock_limit.assert_called_once_with(self.context, user_values=values,
project_values=values,
project_id=fake_inst.project_id,
user_id=fake_inst.user_id)
# Should never reach these.
mock_record.assert_not_called()
mock_resize.assert_not_called()
@mock.patch.object(flavors, 'get_flavor_by_flavor_id')
@mock.patch.object(compute_utils, 'upsize_quota_delta')
@mock.patch.object(quotas_obj.Quotas, 'count_as_dict')
@mock.patch.object(quotas_obj.Quotas, 'limit_check_project_and_user')
def test_resize_quota_exceeds_fails_instance(self, mock_check, mock_count,
mock_upsize, mock_flavor):
fake_inst = self._create_instance_obj()
fake_flavor = self._create_flavor(id=200, flavorid='flavor-id',
name='foo', disabled=False)
mock_flavor.return_value = fake_flavor
deltas = dict(cores=1, ram=1)
mock_upsize.return_value = deltas
quotas = {'instances': 1, 'cores': -1, 'ram': -1}
overs = ['ram']
over_quota_args = dict(quotas=quotas,
usages={'instances': 1, 'cores': 1, 'ram': 512},
overs=overs)
mock_check.side_effect = exception.OverQuota(**over_quota_args)
with mock.patch.object(fake_inst, 'save') as mock_save:
self.assertRaises(exception.TooManyInstances,
self.compute_api.resize, self.context,