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:
parent
bcb96f362a
commit
a263fa46f8
@ -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:
|
||||
|
@ -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])
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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".'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user