Allow unshelve to a specific host (Compute API part)

This patch introduce changes to the compute API that will allow
PROJECT_ADMIN to unshelve an shelved offloaded server to a specific host.
This patch also supports the ability to unpin the availability_zone of an
instance that is bound to it.

Implements: blueprint unshelve-to-host
Change-Id: Ieb4766fdd88c469574fad823e05fe401537cdc30
This commit is contained in:
René Ribaud 2022-06-13 15:20:51 +02:00
parent bcb96f362a
commit a263fa46f8
6 changed files with 747 additions and 62 deletions

View File

@ -95,14 +95,15 @@ class ShelveController(wsgi.Controller):
context.can(shelve_policies.POLICY_ROOT % 'unshelve', context.can(shelve_policies.POLICY_ROOT % 'unshelve',
target={'project_id': instance.project_id}) target={'project_id': instance.project_id})
new_az = None unshelve_args = {}
unshelve_dict = body['unshelve'] unshelve_dict = body['unshelve']
support_az = api_version_request.is_supported(req, '2.77') support_az = api_version_request.is_supported(req, '2.77')
if support_az and unshelve_dict: if support_az and unshelve_dict:
new_az = unshelve_dict['availability_zone'] unshelve_args['new_az'] = unshelve_dict['availability_zone']
try: try:
self.compute_api.unshelve(context, instance, new_az=new_az) self.compute_api.unshelve(context, instance, **unshelve_args)
except (exception.InstanceIsLocked, except (exception.InstanceIsLocked,
exception.UnshelveInstanceInvalidState, exception.UnshelveInstanceInvalidState,
exception.MismatchVolumeAZException) as e: exception.MismatchVolumeAZException) as e:

View File

@ -380,6 +380,8 @@ def block_extended_resource_request(function):
class API: class API:
"""API for interacting with the compute manager.""" """API for interacting with the compute manager."""
_sentinel = object()
def __init__(self, image_api=None, network_api=None, volume_api=None): def __init__(self, image_api=None, network_api=None, volume_api=None):
self.image_api = image_api or glance.API() self.image_api = image_api or glance.API()
self.network_api = network_api or neutron.API() self.network_api = network_api or neutron.API()
@ -4391,31 +4393,45 @@ class API:
context, instance=instance, context, instance=instance,
clean_shutdown=clean_shutdown, accel_uuids=accel_uuids) clean_shutdown=clean_shutdown, accel_uuids=accel_uuids)
def _validate_unshelve_az(self, context, instance, availability_zone): def _check_offloaded(self, context, instance):
"""Verify the specified availability_zone during unshelve. """Check if the status of an instance is SHELVE_OFFLOADED,
if not raise an exception.
Verifies that the server is shelved offloaded, the AZ exists and
if [cinder]/cross_az_attach=False, that any attached volumes are in
the same AZ.
:param context: nova auth RequestContext for the unshelve action
:param instance: Instance object for the server being unshelved
:param availability_zone: The user-requested availability zone in
which to unshelve the server.
:raises: UnshelveInstanceInvalidState if the server is not shelved
offloaded
:raises: InvalidRequest if the requested AZ does not exist
:raises: MismatchVolumeAZException if [cinder]/cross_az_attach=False
and any attached volumes are not in the requested AZ
""" """
if instance.vm_state != vm_states.SHELVED_OFFLOADED: if instance.vm_state != vm_states.SHELVED_OFFLOADED:
# NOTE(brinzhang): If the server status is 'SHELVED', it still # NOTE(brinzhang): If the server status is 'SHELVED', it still
# belongs to a host, the availability_zone has not changed. # belongs to a host, the availability_zone should not change.
# Unshelving a shelved offloaded server will go through the # Unshelving a shelved offloaded server will go through the
# scheduler to find a new host. # scheduler to find a new host.
raise exception.UnshelveInstanceInvalidState( raise exception.UnshelveInstanceInvalidState(
state=instance.vm_state, instance_uuid=instance.uuid) state=instance.vm_state, instance_uuid=instance.uuid)
def _ensure_host_in_az(self, context, host, availability_zone):
"""Ensure the host provided belongs to the availability zone,
if not raise an exception.
"""
if availability_zone is not None:
host_az = availability_zones.get_host_availability_zone(
context,
host
)
if host_az != availability_zone:
raise exception.UnshelveHostNotInAZ(
host=host, availability_zone=availability_zone)
def _validate_unshelve_az(self, context, instance, availability_zone):
"""Verify the specified availability_zone during unshelve.
Verifies the AZ exists and if [cinder]/cross_az_attach=False, that
any attached volumes are in the same AZ.
:param context: nova auth RequestContext for the unshelve action
:param instance: Instance object for the server being unshelved
:param availability_zone: The user-requested availability zone in
which to unshelve the server.
:raises: InvalidRequest if the requested AZ does not exist
:raises: MismatchVolumeAZException if [cinder]/cross_az_attach=False
and any attached volumes are not in the requested AZ
"""
available_zones = availability_zones.get_availability_zones( available_zones = availability_zones.get_availability_zones(
context, self.host_api, get_only_available=True) context, self.host_api, get_only_available=True)
if availability_zone not in available_zones: if availability_zone not in available_zones:
@ -4443,31 +4459,88 @@ class API:
@block_extended_resource_request @block_extended_resource_request
@check_instance_lock @check_instance_lock
@check_instance_state(vm_state=[vm_states.SHELVED, @check_instance_state(
vm_states.SHELVED_OFFLOADED]) vm_state=[vm_states.SHELVED, vm_states.SHELVED_OFFLOADED])
def unshelve(self, context, instance, new_az=None): def unshelve(
"""Restore a shelved instance.""" self, context, instance, new_az=_sentinel, host=None):
"""Restore a shelved instance.
:param context: the nova request context
:param instance: nova.objects.instance.Instance object
:param new_az: (optional) target AZ.
If None is provided then the current AZ restriction
will be removed from the instance.
If the parameter is not provided then the current
AZ restriction will not be changed.
:param host: (optional) a host to target
"""
# Unshelving a shelved offloaded server will go through the
# scheduler to pick a new host, so we update the
# RequestSpec.availability_zone here. Note that if scheduling
# fails the RequestSpec will remain updated, which is not great.
# Bug open to track this https://bugs.launchpad.net/nova/+bug/1978573
az_passed = new_az is not self._sentinel
request_spec = objects.RequestSpec.get_by_instance_uuid( request_spec = objects.RequestSpec.get_by_instance_uuid(
context, instance.uuid) context, instance.uuid)
if new_az: # We need to check a list of preconditions and validate inputs first
# Ensure instance is shelve offloaded
if az_passed or host:
self._check_offloaded(context, instance)
if az_passed and new_az:
# we have to ensure that new AZ is valid
self._validate_unshelve_az(context, instance, new_az) self._validate_unshelve_az(context, instance, new_az)
LOG.debug("Replace the old AZ %(old_az)s in RequestSpec " # This will be the AZ of the instance after the unshelve. It can be
"with a new AZ %(new_az)s of the instance.", # None indicating that the instance is not pinned to any AZ after the
{"old_az": request_spec.availability_zone, # unshelve
"new_az": new_az}, instance=instance) expected_az_after_unshelve = (
# Unshelving a shelved offloaded server will go through the request_spec.availability_zone
# scheduler to pick a new host, so we update the if not az_passed else new_az
# RequestSpec.availability_zone here. Note that if scheduling )
# fails the RequestSpec will remain updated, which is not great, # host is requested, so we have to see if it exists and does not
# but if we want to change that we need to defer updating the # contradict with the AZ of the instance
# RequestSpec until conductor which probably means RPC changes to if host:
# pass the new_az variable to conductor. This is likely low # Ensure that the requested host exists otherwise raise
# priority since the RequestSpec.availability_zone on a shelved # a ComputeHostNotFound exception
# offloaded server does not mean much anyway and clearly the user objects.ComputeNode.get_first_node_by_host_for_old_compat(
# is trying to put the server in the target AZ. context, host, use_slave=True)
request_spec.availability_zone = new_az # A specific host is requested so we need to make sure that it is
request_spec.save() # not contradicts with the AZ of the instance
self._ensure_host_in_az(
context, host, expected_az_after_unshelve)
if new_az is None:
LOG.debug(
'Unpin instance from AZ "%(old_az)s".',
{'old_az': request_spec.availability_zone},
instance=instance
)
LOG.debug(
'Unshelving instance with old availability_zone "%(old_az)s" to '
'new availability_zone "%(new_az)s" and host "%(host)s".',
{
'old_az': request_spec.availability_zone,
'new_az': '%s' %
new_az if az_passed
else 'not provided',
'host': host,
},
instance=instance,
)
# OK every precondition checks out, we just need to tell the scheduler
# where to put the instance
# We have the expected AZ already calculated. So we just need to
# set it in the request_spec to drive the scheduling
request_spec.availability_zone = expected_az_after_unshelve
# if host is requested we also need to tell the scheduler that
if host:
request_spec.requested_destination = objects.Destination(host=host)
request_spec.save()
instance.task_state = task_states.UNSHELVING instance.task_state = task_states.UNSHELVING
instance.save(expected_task_state=[None]) instance.save(expected_task_state=[None])

View File

@ -1022,6 +1022,12 @@ class ComputeTaskManager:
scheduler_utils.populate_filter_properties( scheduler_utils.populate_filter_properties(
filter_properties, selection) filter_properties, selection)
(host, node) = (selection.service_host, selection.nodename) (host, node) = (selection.service_host, selection.nodename)
LOG.debug(
"Scheduler selected host: %s, node:%s",
host,
node,
instance=instance
)
instance.availability_zone = ( instance.availability_zone = (
availability_zones.get_host_availability_zone( availability_zones.get_host_availability_zone(
context, host)) context, host))

View File

@ -1676,9 +1676,15 @@ class MismatchVolumeAZException(Invalid):
class UnshelveInstanceInvalidState(InstanceInvalidState): class UnshelveInstanceInvalidState(InstanceInvalidState):
msg_fmt = _('Specifying an availability zone when unshelving server ' msg_fmt = _('Specifying an availability zone or a host when unshelving '
'%(instance_uuid)s with status "%(state)s" is not supported. ' 'server "%(instance_uuid)s" with status "%(state)s" is not '
'The server status must be SHELVED_OFFLOADED.') 'supported. The server status must be SHELVED_OFFLOADED.')
code = 409
class UnshelveHostNotInAZ(Invalid):
msg_fmt = _('Host "%(host)s" is not in the availability zone '
'"%(availability_zone)s".')
code = 409 code = 409

View File

@ -140,7 +140,9 @@ class UnshelveServerControllerTestV277(test.NoDBTestCase):
'unshelve') as mock_unshelve: 'unshelve') as mock_unshelve:
self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body) self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body)
mock_unshelve.assert_called_once_with( mock_unshelve.assert_called_once_with(
self.req.environ['nova.context'], instance, new_az=None) self.req.environ['nova.context'],
instance,
)
@mock.patch('nova.compute.api.API.unshelve') @mock.patch('nova.compute.api.API.unshelve')
@mock.patch('nova.api.openstack.common.get_instance') @mock.patch('nova.api.openstack.common.get_instance')
@ -158,7 +160,9 @@ class UnshelveServerControllerTestV277(test.NoDBTestCase):
APIVersionRequest('2.76')) APIVersionRequest('2.76'))
self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body) self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body)
mock_unshelve.assert_called_once_with( mock_unshelve.assert_called_once_with(
self.req.environ['nova.context'], instance, new_az=None) self.req.environ['nova.context'],
instance,
)
@mock.patch('nova.compute.api.API.unshelve') @mock.patch('nova.compute.api.API.unshelve')
@mock.patch('nova.api.openstack.common.get_instance') @mock.patch('nova.api.openstack.common.get_instance')

View File

@ -11,10 +11,10 @@
# under the License. # under the License.
import eventlet import eventlet
import mock
from oslo_utils import fixture as utils_fixture from oslo_utils import fixture as utils_fixture
from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import timeutils from oslo_utils import timeutils
from unittest import mock
from nova.compute import api as compute_api from nova.compute import api as compute_api
from nova.compute import claims from nova.compute import claims
@ -24,6 +24,7 @@ from nova.compute import task_states
from nova.compute import utils as compute_utils from nova.compute import utils as compute_utils
from nova.compute import vm_states from nova.compute import vm_states
import nova.conf import nova.conf
from nova import context
from nova.db.main import api as db from nova.db.main import api as db
from nova import exception from nova import exception
from nova.network import neutron as neutron_api from nova.network import neutron as neutron_api
@ -849,9 +850,67 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase):
exclude_states = set() exclude_states = set()
return vm_state - exclude_states return vm_state - exclude_states
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'aggregate_add_host')
@mock.patch('nova.availability_zones.get_availability_zones')
def _create_host_inside_az(
self,
ctxt,
host,
az,
mock_az,
mock_aggregate,
):
self.api = compute_api.AggregateAPI()
mock_az.return_value = [az]
cells = objects.CellMappingList.get_all(ctxt)
cell = cells[0]
with context.target_cell(ctxt, cell) as cctxt:
s = objects.Service(context=cctxt,
host=host,
binary='nova-compute',
topic='compute',
report_count=0)
s.create()
hm = objects.HostMapping(context=ctxt,
cell_mapping=cell,
host=host)
hm.create()
self._init_aggregate_with_host(None, 'fake_aggregate1',
az, host)
def _create_request_spec_for_initial_az(self, az):
fake_spec = objects.RequestSpec()
fake_spec.availability_zone = az
return fake_spec
def _assert_unshelving_and_request_spec_az_and_host(
self,
context,
instance,
fake_spec,
fake_zone,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
):
mock_get_by_instance_uuid.assert_called_once_with(context,
instance.uuid)
mock_unshelve.assert_called_once_with(context, instance, fake_spec)
self.assertEqual(instance.task_state, task_states.UNSHELVING)
self.assertEqual(fake_spec.availability_zone, fake_zone)
if fake_host:
self.assertEqual(fake_spec.requested_destination.host, fake_host)
def _test_shelve(self, vm_state=vm_states.ACTIVE, boot_from_volume=False, def _test_shelve(self, vm_state=vm_states.ACTIVE, boot_from_volume=False,
clean_shutdown=True): clean_shutdown=True):
# Ensure instance can be shelved.
params = dict(task_state=None, vm_state=vm_state, display_name='vm01') params = dict(task_state=None, vm_state=vm_state, display_name='vm01')
fake_instance = self._create_fake_instance_obj(params=params) fake_instance = self._create_fake_instance_obj(params=params)
instance = fake_instance instance = fake_instance
@ -988,12 +1047,14 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase):
return instance return instance
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve(self, get_by_instance_uuid): def test_unshelve(self, get_by_instance_uuid, fake_save):
# Ensure instance can be unshelved. # Ensure instance can be unshelved.
instance = self._get_specify_state_instance(vm_states.SHELVED) instance = self._get_specify_state_instance(vm_states.SHELVED)
fake_spec = objects.RequestSpec() fake_spec = objects.RequestSpec()
fake_spec.availability_zone = None
get_by_instance_uuid.return_value = fake_spec get_by_instance_uuid.return_value = fake_spec
with mock.patch.object(self.compute_api.compute_task_api, with mock.patch.object(self.compute_api.compute_task_api,
'unshelve_instance') as unshelve: 'unshelve_instance') as unshelve:
@ -1116,24 +1177,558 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase):
mock_get_bdms.assert_called_once_with(self.context, instance.uuid) mock_get_bdms.assert_called_once_with(self.context, instance.uuid)
mock_get.assert_called_once_with(self.context, uuids.volume_id) mock_get.assert_called_once_with(self.context, uuids.volume_id)
@mock.patch.object(compute_api.API, '_validate_unshelve_az') # Next tests attempt to check the following behavior
# +----------+---------------------------+-------+----------------------------+
# | Boot | Unshelve after offload AZ | Host | Result |
# +==========+===========================+=======+============================+
# | No AZ | No AZ or AZ=null | No | Free scheduling, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
# | No AZ | No AZ or AZ=null | Host1 | Schedule to host1, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
# | No AZ | AZ="AZ1" | No | Schedule to AZ1, |
# | | | | reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
# | No AZ | AZ="AZ1" | Host1 | Verify that host1 in AZ1, |
# | | | | or (1). Schedule to |
# | | | | host1, reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | No AZ | No | Schedule to AZ1, |
# | | | | reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ=null | No | Free scheduling, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | No AZ | Host1 | If host1 is in AZ1, |
# | | | | then schedule to host1, |
# | | | | reqspec.AZ="AZ1", otherwise|
# | | | | reject the request (1) |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ=null | Host1 | Schedule to host1, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ="AZ2" | No | Schedule to AZ2, |
# | | | | reqspec.AZ="AZ2" |
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ="AZ2" | Host1 | If host1 in AZ2 then |
# | | | | schedule to host1, |
# | | | | reqspec.AZ="AZ2", |
# | | | | otherwise reject (1) |
# +----------+---------------------------+-------+----------------------------+
#
# (1) Check at the api and return an error.
#
#
# +----------+---------------------------+-------+----------------------------+
# | No AZ | No AZ or AZ=null | No | Free scheduling, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save') @mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_specified_az_unshelve(self, get_by_instance_uuid, def test_unshelve_without_az(
mock_save, mock_validate_unshelve_az): self,
# Ensure instance can be unshelved. mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance( instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED) vm_states.SHELVED_OFFLOADED)
new_az = "west_az" fake_spec = self._create_request_spec_for_initial_az(None)
fake_spec = objects.RequestSpec() mock_get_by_instance_uuid.return_value = fake_spec
fake_spec.availability_zone = "fake-old-az"
get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(self.context, instance, new_az=new_az) self.compute_api.unshelve(context, instance)
mock_save.assert_called_once_with() self._assert_unshelving_and_request_spec_az_and_host(
self.assertEqual(new_az, fake_spec.availability_zone) context,
instance,
fake_spec,
None,
None,
mock_get_by_instance_uuid,
mock_unshelve
)
mock_validate_unshelve_az.assert_called_once_with( # +----------+---------------------------+-------+----------------------------+
self.context, instance, new_az) # | No AZ | No AZ or AZ=null | Host1 | Schedule to host1, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_without_az_to_host(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(None)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(context, instance, host=fake_host)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
None,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | No AZ | AZ="AZ1" | No | Schedule to AZ1, |
# | | | | reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_without_az_to_newaz(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(None)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(context, instance, new_az=fake_zone)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
None,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | No AZ | AZ="AZ1" | Host1 | Verify that host1 in AZ1, |
# | | | | or (1). Schedule to |
# | | | | host1, reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_without_az_to_newaz_and_host(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(None)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(
context, instance, new_az=fake_zone, host=fake_host
)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
)
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_without_az_to_newaz_and_host_invalid(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(None)
mock_get_by_instance_uuid.return_value = fake_spec
exc = self.assertRaises(
nova.exception.UnshelveHostNotInAZ,
self.compute_api.unshelve,
context,
instance,
new_az='avail_zone1',
host='fake_mini'
)
self.assertIn(
exc.message,
'Host "fake_mini" is not in the availability zone "avail_zone1".'
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | No AZ | No | Schedule to AZ1, |
# | | | | reqspec.AZ="AZ1" |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(fake_zone)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(context, instance)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
None,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ=null | No | Free scheduling, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_unpin_az(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(fake_zone)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(context, instance, new_az=None)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
None,
None,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | No AZ | Host1 | If host1 is in AZ1, |
# | | | | then schedule to host1, |
# | | | | reqspec.AZ="AZ1", otherwise|
# | | | | reject the request (1) |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_host_in_az(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(fake_zone)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(context, instance, host=fake_host)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
)
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_invalid_host(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(fake_zone)
mock_get_by_instance_uuid.return_value = fake_spec
exc = self.assertRaises(
nova.exception.UnshelveHostNotInAZ,
self.compute_api.unshelve,
context,
instance,
host='fake_mini'
)
self.assertIn(
exc.message,
'Host "fake_mini" is not in the availability zone "avail_zone1".'
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ=null | Host1 | Schedule to host1, |
# | | | | reqspec.AZ=None |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_host_unpin_az(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az(fake_zone)
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(
context, instance, new_az=None, host=fake_host
)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
None,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ="AZ2" | No | Schedule to AZ2, |
# | | | | reqspec.AZ="AZ2" |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_newaz(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az('az1')
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(
context, instance, new_az=fake_zone
)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
None,
mock_get_by_instance_uuid,
mock_unshelve
)
# +----------+---------------------------+-------+----------------------------+
# | AZ1 | AZ="AZ2" | Host1 | If host1 in AZ2 then |
# | | | | schedule to host1, |
# | | | | reqspec.AZ="AZ2", |
# | | | | otherwise reject (1) |
# +----------+---------------------------+-------+----------------------------+
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_newaz_and_host(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az('az1')
mock_get_by_instance_uuid.return_value = fake_spec
self.compute_api.unshelve(
context, instance, new_az=fake_zone, host=fake_host
)
self._assert_unshelving_and_request_spec_az_and_host(
context,
instance,
fake_spec,
fake_zone,
fake_host,
mock_get_by_instance_uuid,
mock_unshelve
)
@mock.patch.object(nova.conductor.ComputeTaskAPI, 'unshelve_instance')
@mock.patch.object(objects.RequestSpec, 'save')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_unshelve_with_az_to_newaz_and_invalid_host(
self,
mock_get_by_instance_uuid,
mock_get_all_by_host,
mock_save,
mock_unshelve
):
context = self.context.elevated()
fake_host = 'fake_host1'
fake_zone = 'avail_zone1'
self._create_host_inside_az(self.context, fake_host, fake_zone)
instance = self._get_specify_state_instance(
vm_states.SHELVED_OFFLOADED)
fake_spec = self._create_request_spec_for_initial_az('az1')
mock_get_by_instance_uuid.return_value = fake_spec
exc = self.assertRaises(
nova.exception.UnshelveHostNotInAZ,
self.compute_api.unshelve,
context,
instance,
new_az=fake_zone,
host='fake_mini'
)
self.assertIn(
exc.message,
'Host "fake_mini" is not in the availability zone "avail_zone1".'
)