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',
target={'project_id': instance.project_id})
new_az = None
unshelve_args = {}
unshelve_dict = body['unshelve']
support_az = api_version_request.is_supported(req, '2.77')
if support_az and unshelve_dict:
new_az = unshelve_dict['availability_zone']
unshelve_args['new_az'] = unshelve_dict['availability_zone']
try:
self.compute_api.unshelve(context, instance, new_az=new_az)
self.compute_api.unshelve(context, instance, **unshelve_args)
except (exception.InstanceIsLocked,
exception.UnshelveInstanceInvalidState,
exception.MismatchVolumeAZException) as e:

View File

@ -380,6 +380,8 @@ def block_extended_resource_request(function):
class API:
"""API for interacting with the compute manager."""
_sentinel = object()
def __init__(self, image_api=None, network_api=None, volume_api=None):
self.image_api = image_api or glance.API()
self.network_api = network_api or neutron.API()
@ -4391,31 +4393,45 @@ class API:
context, instance=instance,
clean_shutdown=clean_shutdown, accel_uuids=accel_uuids)
def _validate_unshelve_az(self, context, instance, availability_zone):
"""Verify the specified availability_zone during unshelve.
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
def _check_offloaded(self, context, instance):
"""Check if the status of an instance is SHELVE_OFFLOADED,
if not raise an exception.
"""
if instance.vm_state != vm_states.SHELVED_OFFLOADED:
# 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
# scheduler to find a new host.
raise exception.UnshelveInstanceInvalidState(
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(
context, self.host_api, get_only_available=True)
if availability_zone not in available_zones:
@ -4443,31 +4459,88 @@ class API:
@block_extended_resource_request
@check_instance_lock
@check_instance_state(vm_state=[vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED])
def unshelve(self, context, instance, new_az=None):
"""Restore a shelved instance."""
@check_instance_state(
vm_state=[vm_states.SHELVED, vm_states.SHELVED_OFFLOADED])
def unshelve(
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(
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)
LOG.debug("Replace the old AZ %(old_az)s in RequestSpec "
"with a new AZ %(new_az)s of the instance.",
{"old_az": request_spec.availability_zone,
"new_az": new_az}, instance=instance)
# 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,
# but if we want to change that we need to defer updating the
# RequestSpec until conductor which probably means RPC changes to
# pass the new_az variable to conductor. This is likely low
# priority since the RequestSpec.availability_zone on a shelved
# offloaded server does not mean much anyway and clearly the user
# is trying to put the server in the target AZ.
request_spec.availability_zone = new_az
request_spec.save()
# This will be the AZ of the instance after the unshelve. It can be
# None indicating that the instance is not pinned to any AZ after the
# unshelve
expected_az_after_unshelve = (
request_spec.availability_zone
if not az_passed else new_az
)
# host is requested, so we have to see if it exists and does not
# contradict with the AZ of the instance
if host:
# Ensure that the requested host exists otherwise raise
# a ComputeHostNotFound exception
objects.ComputeNode.get_first_node_by_host_for_old_compat(
context, host, use_slave=True)
# A specific host is requested so we need to make sure that it is
# 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.save(expected_task_state=[None])

View File

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

View File

@ -1676,9 +1676,15 @@ class MismatchVolumeAZException(Invalid):
class UnshelveInstanceInvalidState(InstanceInvalidState):
msg_fmt = _('Specifying an availability zone when unshelving server '
'%(instance_uuid)s with status "%(state)s" is not supported. '
'The server status must be SHELVED_OFFLOADED.')
msg_fmt = _('Specifying an availability zone or a host when unshelving '
'server "%(instance_uuid)s" with status "%(state)s" is not '
'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

View File

@ -140,7 +140,9 @@ class UnshelveServerControllerTestV277(test.NoDBTestCase):
'unshelve') as mock_unshelve:
self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body)
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.api.openstack.common.get_instance')
@ -158,7 +160,9 @@ class UnshelveServerControllerTestV277(test.NoDBTestCase):
APIVersionRequest('2.76'))
self.controller._unshelve(self.req, fakes.FAKE_UUID, body=body)
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.api.openstack.common.get_instance')

View File

@ -11,10 +11,10 @@
# under the License.
import eventlet
import mock
from oslo_utils import fixture as utils_fixture
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import timeutils
from unittest import mock
from nova.compute import api as compute_api
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 vm_states
import nova.conf
from nova import context
from nova.db.main import api as db
from nova import exception
from nova.network import neutron as neutron_api
@ -849,9 +850,67 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase):
exclude_states = set()
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,
clean_shutdown=True):
# Ensure instance can be shelved.
params = dict(task_state=None, vm_state=vm_state, display_name='vm01')
fake_instance = self._create_fake_instance_obj(params=params)
instance = fake_instance
@ -988,12 +1047,14 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase):
return instance
@mock.patch.object(objects.RequestSpec, 'save')
@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.
instance = self._get_specify_state_instance(vm_states.SHELVED)
fake_spec = objects.RequestSpec()
fake_spec.availability_zone = None
get_by_instance_uuid.return_value = fake_spec
with mock.patch.object(self.compute_api.compute_task_api,
'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.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.ComputeNodeList, 'get_all_by_host')
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
def test_specified_az_unshelve(self, get_by_instance_uuid,
mock_save, mock_validate_unshelve_az):
# Ensure instance can be unshelved.
def test_unshelve_without_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)
new_az = "west_az"
fake_spec = objects.RequestSpec()
fake_spec.availability_zone = "fake-old-az"
get_by_instance_uuid.return_value = fake_spec
fake_spec = self._create_request_spec_for_initial_az(None)
mock_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.assertEqual(new_az, fake_spec.availability_zone)
self._assert_unshelving_and_request_spec_az_and_host(
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".'
)