Merge "api: extend evacuate instance to support target state"

This commit is contained in:
Zuul
2023-02-01 11:49:46 +00:00
committed by Gerrit Code Review
27 changed files with 252 additions and 27 deletions

View File

@@ -0,0 +1,5 @@
{
"evacuate": {
"targetState": "stopped"
}
}

View File

@@ -0,0 +1,6 @@
{
"evacuate": {
"host": "testHost",
"targetState": "stopped"
}
}

View File

@@ -19,7 +19,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.94", "version": "2.95",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@@ -22,7 +22,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.94", "version": "2.95",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@@ -254,6 +254,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
in keypair name. in keypair name.
* 2.93 - Add support for volume backed server rebuild. * 2.93 - Add support for volume backed server rebuild.
* 2.94 - Allow FQDN in server hostname. * 2.94 - Allow FQDN in server hostname.
* 2.95 - Evacuate will now stop instance at destination.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@@ -262,7 +263,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions # Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API. # support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = '2.1' _MIN_API_VERSION = '2.1'
_MAX_API_VERSION = '2.94' _MAX_API_VERSION = '2.95'
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal # Almost all proxy APIs which are related to network, images and baremetal

View File

@@ -23,9 +23,11 @@ from nova.api.openstack.compute.schemas import evacuate
from nova.api.openstack import wsgi from nova.api.openstack import wsgi
from nova.api import validation from nova.api import validation
from nova.compute import api as compute from nova.compute import api as compute
from nova.compute import vm_states
import nova.conf import nova.conf
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
from nova import objects
from nova.policies import evacuate as evac_policies from nova.policies import evacuate as evac_policies
from nova import utils from nova import utils
@@ -33,6 +35,8 @@ CONF = nova.conf.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MIN_VER_NOVA_COMPUTE_EVACUATE_STOPPED = 62
class EvacuateController(wsgi.Controller): class EvacuateController(wsgi.Controller):
def __init__(self): def __init__(self):
@@ -77,7 +81,8 @@ class EvacuateController(wsgi.Controller):
@validation.schema(evacuate.evacuate, "2.0", "2.13") @validation.schema(evacuate.evacuate, "2.0", "2.13")
@validation.schema(evacuate.evacuate_v214, "2.14", "2.28") @validation.schema(evacuate.evacuate_v214, "2.14", "2.28")
@validation.schema(evacuate.evacuate_v2_29, "2.29", "2.67") @validation.schema(evacuate.evacuate_v2_29, "2.29", "2.67")
@validation.schema(evacuate.evacuate_v2_68, "2.68") @validation.schema(evacuate.evacuate_v2_68, "2.68", "2.94")
@validation.schema(evacuate.evacuate_v2_95, "2.95")
def _evacuate(self, req, id, body): def _evacuate(self, req, id, body):
"""Permit admins to evacuate a server from a failed host """Permit admins to evacuate a server from a failed host
to a new one. to a new one.
@@ -92,6 +97,19 @@ class EvacuateController(wsgi.Controller):
host = evacuate_body.get("host") host = evacuate_body.get("host")
force = None force = None
target_state = None
if api_version_request.is_supported(req, min_version='2.95'):
min_ver = objects.service.get_minimum_version_all_cells(
context, ['nova-compute'])
if min_ver < MIN_VER_NOVA_COMPUTE_EVACUATE_STOPPED:
raise exception.NotSupportedComputeForEvacuateV295(
{'currently': min_ver,
'expected': MIN_VER_NOVA_COMPUTE_EVACUATE_STOPPED})
# Starts to 2.95 any evacuated instances will be stopped at
# destination. Previously an active or stopped instance would have
# kept its state.
target_state = vm_states.STOPPED
on_shared_storage = self._get_on_shared_storage(req, evacuate_body) on_shared_storage = self._get_on_shared_storage(req, evacuate_body)
if api_version_request.is_supported(req, min_version='2.29'): if api_version_request.is_supported(req, min_version='2.29'):
@@ -120,7 +138,8 @@ class EvacuateController(wsgi.Controller):
try: try:
self.compute_api.evacuate(context, instance, host, self.compute_api.evacuate(context, instance, host,
on_shared_storage, password, force) on_shared_storage, password, force,
target_state)
except exception.InstanceInvalidState as state_error: except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error, common.raise_http_conflict_for_instance_invalid_state(state_error,
'evacuate', id) 'evacuate', id)
@@ -130,6 +149,8 @@ class EvacuateController(wsgi.Controller):
exception.ExtendedResourceRequestOldCompute, exception.ExtendedResourceRequestOldCompute,
) as e: ) as e:
raise exc.HTTPBadRequest(explanation=e.format_message()) raise exc.HTTPBadRequest(explanation=e.format_message())
except exception.UnsupportedRPCVersion as e:
raise exc.HTTPConflict(explanation=e.format_message())
if (not api_version_request.is_supported(req, min_version='2.14') and if (not api_version_request.is_supported(req, min_version='2.14') and
CONF.api.enable_instance_password): CONF.api.enable_instance_password):

View File

@@ -1237,3 +1237,11 @@ The ``hostname`` parameter to the ``POST /servers`` (create server), ``PUT
/servers/{id}`` (update server) and ``POST /servers/{server_id}/action /servers/{id}`` (update server) and ``POST /servers/{server_id}/action
(rebuild)`` (rebuild server) APIs is now allowed to be a Fully Qualified Domain (rebuild)`` (rebuild server) APIs is now allowed to be a Fully Qualified Domain
Name (FQDN). Name (FQDN).
2.95
---------------------
Any evacuated instances will be now stopped at destination. This
requires minimun compute version 27.0.0 (antelope 2023.1). Operators
can still use previous microversion for older behavior.

View File

@@ -46,3 +46,7 @@ evacuate_v2_29['properties']['evacuate']['properties'][
# v2.68 removes the 'force' parameter added in v2.29, meaning it is identical # v2.68 removes the 'force' parameter added in v2.29, meaning it is identical
# to v2.14 # to v2.14
evacuate_v2_68 = copy.deepcopy(evacuate_v214) evacuate_v2_68 = copy.deepcopy(evacuate_v214)
# v2.95 keeps the same schema, evacuating an instance will now result its state
# to be stopped at destination.
evacuate_v2_95 = copy.deepcopy(evacuate_v2_68)

View File

@@ -2510,3 +2510,10 @@ class ReimageException(NovaException):
class InvalidNodeConfiguration(NovaException): class InvalidNodeConfiguration(NovaException):
msg_fmt = _('Invalid node identity configuration: %(reason)s') msg_fmt = _('Invalid node identity configuration: %(reason)s')
class NotSupportedComputeForEvacuateV295(NotSupported):
msg_fmt = _("Starting to microversion 2.95, evacuate API will stop "
"instance on destination. To evacuate before upgrades are "
"complete please use an older microversion. Required version "
"for compute %(expected), current version %(currently)s")

View File

@@ -0,0 +1,5 @@
{
"evacuate": {
"adminPass": "%(adminPass)s"
}
}

View File

@@ -0,0 +1,5 @@
{
"evacuate": {
"adminPass": "%(adminPass)s"
}
}

View File

@@ -216,3 +216,41 @@ class EvacuateJsonTestV268(EvacuateJsonTestV229):
def test_server_evacuate_with_force(self): def test_server_evacuate_with_force(self):
# doesn't apply to v2.68+, which removed the ability to force migrate # doesn't apply to v2.68+, which removed the ability to force migrate
pass pass
class EvacuateJsonTestV295(EvacuateJsonTestV268):
microversion = '2.95'
scenarios = [('v2_95', {'api_major_version': 'v2.1'})]
@mock.patch('nova.conductor.manager.ComputeTaskManager.rebuild_instance')
def test_server_evacuate(self, rebuild_mock):
req_subs = {
"adminPass": "MySecretPass",
}
self._test_evacuate(req_subs, 'server-evacuate-req',
server_resp=None, expected_resp_code=200)
rebuild_mock.assert_called_once_with(mock.ANY, instance=mock.ANY,
orig_image_ref=mock.ANY, image_ref=mock.ANY,
injected_files=mock.ANY, new_pass="MySecretPass",
orig_sys_metadata=mock.ANY, bdms=mock.ANY, recreate=mock.ANY,
on_shared_storage=None, preserve_ephemeral=mock.ANY,
host=None, request_spec=mock.ANY,
reimage_boot_volume=False, target_state="stopped")
@mock.patch('nova.conductor.manager.ComputeTaskManager.rebuild_instance')
def test_server_evacuate_find_host(self, rebuild_mock):
req_subs = {
'host': 'testHost',
"adminPass": "MySecretPass",
}
self._test_evacuate(req_subs, 'server-evacuate-find-host-req',
server_resp=None, expected_resp_code=200)
rebuild_mock.assert_called_once_with(mock.ANY, instance=mock.ANY,
orig_image_ref=mock.ANY, image_ref=mock.ANY,
injected_files=mock.ANY, new_pass="MySecretPass",
orig_sys_metadata=mock.ANY, bdms=mock.ANY, recreate=mock.ANY,
on_shared_storage=None, preserve_ephemeral=mock.ANY,
host=None, request_spec=mock.ANY,
reimage_boot_volume=False, target_state="stopped")

View File

@@ -598,7 +598,7 @@ class InstanceHelperMixin:
def _evacuate_server( def _evacuate_server(
self, server, extra_post_args=None, expected_host=None, self, server, extra_post_args=None, expected_host=None,
expected_state='ACTIVE', expected_task_state=NOT_SPECIFIED, expected_state='SHUTOFF', expected_task_state=NOT_SPECIFIED,
expected_migration_status='done'): expected_migration_status='done'):
"""Evacuate a server.""" """Evacuate a server."""
api = getattr(self, 'admin_api', self.api) api = getattr(self, 'admin_api', self.api)

View File

@@ -10,6 +10,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from unittest import mock
from nova import objects
from nova.tests import fixtures from nova.tests import fixtures
from nova.tests.functional.notification_sample_tests \ from nova.tests.functional.notification_sample_tests \
import notification_sample_base import notification_sample_base
@@ -53,6 +56,10 @@ class TestComputeTaskNotificationSample(
}, },
actual=self.notifier.versioned_notifications[1]) actual=self.notifier.versioned_notifications[1])
@mock.patch.object(
objects.service, 'get_minimum_version_all_cells',
new=mock.Mock(return_value=62)
)
def test_rebuild_fault(self): def test_rebuild_fault(self):
server = self._boot_a_server( server = self._boot_a_server(
extra_params={'networks': [{'port': self.neutron.port_1['id']}]}, extra_params={'networks': [{'port': self.neutron.port_1['id']}]},

View File

@@ -46,18 +46,18 @@ class TestInstanceNotificationSampleWithMultipleCompute(
self.compute2 = self.start_service('compute', host='host2') self.compute2 = self.start_service('compute', host='host2')
actions = [ actions = [
self._test_live_migration_rollback, (self._test_live_migration_rollback, 'ACTIVE'),
self._test_live_migration_abort, (self._test_live_migration_abort, 'ACTIVE'),
self._test_live_migration_success, (self._test_live_migration_success, 'ACTIVE'),
self._test_evacuate_server, (self._test_evacuate_server, 'SHUTOFF'),
self._test_live_migration_force_complete (self._test_live_migration_force_complete, 'ACTIVE'),
] ]
for action in actions: for action, expected_state in actions:
self.notifier.reset() self.notifier.reset()
action(server) action(server)
# Ensure that instance is in active state after an action # Ensure that instance is in active state after an action
self._wait_for_state_change(server, 'ACTIVE') self._wait_for_state_change(server, expected_state)
@mock.patch('nova.compute.manager.ComputeManager.' @mock.patch('nova.compute.manager.ComputeManager.'
'_live_migration_cleanup_flags', return_value=[True, False]) '_live_migration_cleanup_flags', return_value=[True, False])
@@ -275,6 +275,12 @@ class TestInstanceNotificationSampleWithMultipleCompute(
self.admin_api.put_service(service_id, {'forced_down': False}) self.admin_api.put_service(service_id, {'forced_down': False})
def _test_live_migration_force_complete(self, server): def _test_live_migration_force_complete(self, server):
# In the scenario evacuate happened before which stopped the
# server.
self._start_server(server)
self._wait_for_state_change(server, 'ACTIVE')
self.notifier.reset()
post = { post = {
'os-migrateLive': { 'os-migrateLive': {
'host': 'host2', 'host': 'host2',

View File

@@ -59,7 +59,8 @@ class ResizeEvacuateTestCase(integrated_helpers._IntegratedTestBase):
# Now try to evacuate the server back to the original source compute. # Now try to evacuate the server back to the original source compute.
server = self._evacuate_server( server = self._evacuate_server(
server, {'onSharedStorage': 'False'}, server, {'onSharedStorage': 'False'},
expected_host=self.compute.host, expected_migration_status='done') expected_host=self.compute.host, expected_migration_status='done',
expected_state='ACTIVE')
# Assert the RequestSpec.ignore_hosts field is not populated. # Assert the RequestSpec.ignore_hosts field is not populated.
reqspec = objects.RequestSpec.get_by_instance_uuid( reqspec = objects.RequestSpec.get_by_instance_uuid(

View File

@@ -13,9 +13,11 @@
# limitations under the License. # limitations under the License.
import time import time
from unittest import mock
from oslo_log import log as logging from oslo_log import log as logging
from nova import objects
from nova import test from nova import test
from nova.tests import fixtures as nova_fixtures from nova.tests import fixtures as nova_fixtures
from nova.tests.functional import fixtures as func_fixtures from nova.tests.functional import fixtures as func_fixtures
@@ -81,6 +83,10 @@ class FailedEvacuateStateTests(test.TestCase,
created_server = self.api.post_server({'server': server_req}) created_server = self.api.post_server({'server': server_req})
return self._wait_for_state_change(created_server, 'ACTIVE') return self._wait_for_state_change(created_server, 'ACTIVE')
@mock.patch.object(
objects.service, 'get_minimum_version_all_cells',
new=mock.Mock(return_value=62)
)
def test_evacuate_no_valid_host(self): def test_evacuate_no_valid_host(self):
# Boot a server # Boot a server
server = self._boot_a_server() server = self._boot_a_server()

View File

@@ -95,7 +95,8 @@ class TestEvacuationWithSourceReturningDuringRebuild(
# Evacuate the instance from the source_host # Evacuate the instance from the source_host
server = self._evacuate_server( server = self._evacuate_server(
server, expected_migration_status='done') server, expected_migration_status='done',
expected_state='ACTIVE')
host = server['OS-EXT-SRV-ATTR:host'] host = server['OS-EXT-SRV-ATTR:host']
migrations = self.api.get_migrations() migrations = self.api.get_migrations()

View File

@@ -66,4 +66,5 @@ class MultiCellEvacuateTestCase(integrated_helpers._IntegratedTestBase):
# higher than host3. # higher than host3.
self._evacuate_server( self._evacuate_server(
server, {'onSharedStorage': 'False'}, expected_host='host3', server, {'onSharedStorage': 'False'}, expected_host='host3',
expected_migration_status='done') expected_migration_status='done',
expected_state='ACTIVE')

View File

@@ -216,7 +216,7 @@ class TestEvacuateResourceTrackerRace(
self._run_periodics() self._run_periodics()
self._wait_for_server_parameter( self._wait_for_server_parameter(
server, {'OS-EXT-SRV-ATTR:host': 'host2', 'status': 'ACTIVE'}) server, {'OS-EXT-SRV-ATTR:host': 'host2', 'status': 'SHUTOFF'})
self._assert_pci_device_allocated(server['id'], self.compute1_id) self._assert_pci_device_allocated(server['id'], self.compute1_id)
self._assert_pci_device_allocated(server['id'], self.compute2_id) self._assert_pci_device_allocated(server['id'], self.compute2_id)

View File

@@ -1,3 +1,4 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@@ -27,6 +28,7 @@ class ForceUpWithDoneEvacuations(integrated_helpers._IntegratedTestBase):
ADMIN_API = True ADMIN_API = True
microversion = 'latest' microversion = 'latest'
expected_state = 'SHUTOFF'
def _create_test_server(self, compute_host): def _create_test_server(self, compute_host):
return self._create_server(host=compute_host, networks='none') return self._create_server(host=compute_host, networks='none')
@@ -59,7 +61,8 @@ class ForceUpWithDoneEvacuations(integrated_helpers._IntegratedTestBase):
server = self._evacuate_server( server = self._evacuate_server(
server, server,
expected_host='compute2', expected_host='compute2',
expected_migration_status='done' expected_migration_status='done',
expected_state=self.expected_state
) )
# Assert that the request to force up the host is rejected # Assert that the request to force up the host is rejected
@@ -97,6 +100,7 @@ class ForceUpWithDoneEvacuationsv252(ForceUpWithDoneEvacuations):
""" """
microversion = '2.52' microversion = '2.52'
expected_state = 'ACTIVE'
def _create_test_server(self, compute_host): def _create_test_server(self, compute_host):
return self._create_server(az='nova:compute', networks='none') return self._create_server(az='nova:compute', networks='none')

View File

@@ -444,7 +444,8 @@ class ServerGroupTestV21(ServerGroupTestBase):
evacuated_server = self._evacuate_server( evacuated_server = self._evacuate_server(
servers[1], {'onSharedStorage': 'False'}, servers[1], {'onSharedStorage': 'False'},
expected_migration_status='done') expected_migration_status='done',
expected_state='ACTIVE')
# check that the server is evacuated to another host # check that the server is evacuated to another host
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'], self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
@@ -621,7 +622,8 @@ class ServerGroupTestV215(ServerGroupTestV21):
compute3 = self.start_service('compute', host='host3') compute3 = self.start_service('compute', host='host3')
evacuated_server = self._evacuate_server( evacuated_server = self._evacuate_server(
servers[1], expected_migration_status='done') servers[1], expected_migration_status='done',
expected_state='ACTIVE')
# check that the server is evacuated # check that the server is evacuated
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'], self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
@@ -800,7 +802,8 @@ class ServerGroupTestV215(ServerGroupTestV21):
self._set_forced_down(host, True) self._set_forced_down(host, True)
evacuated_server = self._evacuate_server( evacuated_server = self._evacuate_server(
servers[1], expected_migration_status='done') servers[1], expected_migration_status='done',
expected_state='ACTIVE')
# Note(gibi): need to get the server again as the state of the instance # Note(gibi): need to get the server again as the state of the instance
# goes to ACTIVE first then the host of the instance changes to the # goes to ACTIVE first then the host of the instance changes to the
@@ -870,6 +873,54 @@ class ServerGroupTestV264(ServerGroupTestV215):
self.assertEqual(2, hosts.count(host)) self.assertEqual(2, hosts.count(host))
class ServerGroupTestV295(ServerGroupTestV264):
microversion = '2.95'
def _evacuate_with_soft_anti_affinity_policies(self, group):
created_group = self.api.post_server_groups(group)
servers = self._boot_servers_to_group(created_group)
host = self._get_compute_service_by_host_name(
servers[1]['OS-EXT-SRV-ATTR:host'])
# Set forced_down on the host to ensure nova considers the host down.
self._set_forced_down(host, True)
evacuated_server = self._evacuate_server(
servers[1], expected_migration_status='done')
# Note(gibi): need to get the server again as the state of the instance
# goes to ACTIVE first then the host of the instance changes to the
# new host later
evacuated_server = self.admin_api.get_server(evacuated_server['id'])
return [evacuated_server['OS-EXT-SRV-ATTR:host'],
servers[0]['OS-EXT-SRV-ATTR:host']]
def test_evacuate_with_anti_affinity(self):
created_group = self.api.post_server_groups(self.anti_affinity)
servers = self._boot_servers_to_group(created_group)
host = self._get_compute_service_by_host_name(
servers[1]['OS-EXT-SRV-ATTR:host'])
# Set forced_down on the host to ensure nova considers the host down.
self._set_forced_down(host, True)
# Start additional host to test evacuation
compute3 = self.start_service('compute', host='host3')
evacuated_server = self._evacuate_server(
servers[1], expected_migration_status='done')
# check that the server is evacuated
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
servers[1]['OS-EXT-SRV-ATTR:host'])
# check that policy is kept
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
servers[0]['OS-EXT-SRV-ATTR:host'])
compute3.kill()
class ServerGroupTestMultiCell(ServerGroupTestBase): class ServerGroupTestMultiCell(ServerGroupTestBase):
NUMBER_OF_CELLS = 2 NUMBER_OF_CELLS = 2

View File

@@ -2260,7 +2260,8 @@ class ServerMovingTests(integrated_helpers.ProviderUsageBaseTestCase):
} }
server = self._evacuate_server( server = self._evacuate_server(
server, extra_post_args=post, expected_host=dest_hostname) server, extra_post_args=post, expected_host=dest_hostname,
expected_state='ACTIVE')
# Run the periodics to show those don't modify allocations. # Run the periodics to show those don't modify allocations.
self._run_periodics() self._run_periodics()
@@ -2437,7 +2438,8 @@ class ServerMovingTests(integrated_helpers.ProviderUsageBaseTestCase):
# stay ACTIVE and task_state will be set to None. # stay ACTIVE and task_state will be set to None.
server = self._evacuate_server( server = self._evacuate_server(
server, expected_task_state=None, server, expected_task_state=None,
expected_migration_status='failed') expected_migration_status='failed',
expected_state='ACTIVE')
# Run the periodics to show those don't modify allocations. # Run the periodics to show those don't modify allocations.
self._run_periodics() self._run_periodics()
@@ -5324,7 +5326,8 @@ class ServerMovingTestsWithNestedResourceRequests(
server = self._evacuate_server( server = self._evacuate_server(
server, extra_post_args=post, expected_migration_status='error', server, extra_post_args=post, expected_migration_status='error',
expected_host=source_hostname) expected_host=source_hostname,
expected_state='ACTIVE')
self.assertIn('Unable to move instance %s to host host2. The instance ' self.assertIn('Unable to move instance %s to host host2. The instance '
'has complex allocations on the source host so move ' 'has complex allocations on the source host so move '
@@ -5530,7 +5533,8 @@ class ServerMovingTestsFromFlatToNested(
self._evacuate_server( self._evacuate_server(
server, extra_post_args=post, expected_host='host1', server, extra_post_args=post, expected_host='host1',
expected_migration_status='error') expected_migration_status='error',
expected_state='ACTIVE')
# We expect that the evacuation will fail as force evacuate tries to # We expect that the evacuation will fail as force evacuate tries to
# blindly copy the source allocation to the destination but on the # blindly copy the source allocation to the destination but on the

View File

@@ -2162,7 +2162,8 @@ class ServerMoveWithPortResourceRequestTest(
# simply fail and the server remains on the source host # simply fail and the server remains on the source host
server = self._evacuate_server( server = self._evacuate_server(
server, expected_host='host1', expected_task_state=None, server, expected_host='host1', expected_task_state=None,
expected_migration_status='failed') expected_migration_status='failed',
expected_state="ACTIVE")
# As evacuation failed the resource allocation should be untouched # As evacuation failed the resource allocation should be untouched
self._check_allocation( self._check_allocation(
@@ -2979,6 +2980,7 @@ class ExtendedResourceRequestOldCompute(
super().setUp() super().setUp()
self.neutron = self.useFixture( self.neutron = self.useFixture(
ExtendedResourceRequestNeutronFixture(self)) ExtendedResourceRequestNeutronFixture(self))
self.api.microversion = '2.72'
@mock.patch.object( @mock.patch.object(
objects.service, 'get_minimum_version_all_cells', objects.service, 'get_minimum_version_all_cells',

View File

@@ -416,3 +416,32 @@ class EvacuateTestV268(EvacuateTestV229):
def test_forced_evacuate_with_no_host_provided(self): def test_forced_evacuate_with_no_host_provided(self):
# not applicable for v2.68, which removed the 'force' parameter # not applicable for v2.68, which removed the 'force' parameter
pass pass
class EvacuateTestV295(EvacuateTestV268):
def setUp(self):
super(EvacuateTestV268, self).setUp()
self.admin_req = fakes.HTTPRequest.blank('', use_admin_context=True,
version='2.95')
self.req = fakes.HTTPRequest.blank('', version='2.95')
self.mock_get_min_ver = self.useFixture(fixtures.MockPatch(
'nova.objects.service.get_minimum_version_all_cells',
return_value=62)).mock
def test_evacuate_version_error(self):
self.mock_get_min_ver.return_value = 61
self.assertRaises(webob.exc.HTTPBadRequest,
self._get_evacuate_response,
{'host': 'my-host', 'adminPass': 'foo'})
def test_evacuate_unsupported_rpc(self):
def fake_evacuate(*args, **kwargs):
raise exception.UnsupportedRPCVersion(
api="fakeapi",
required="x.xx")
self.stub_out('nova.compute.api.API.evacuate', fake_evacuate)
self._check_evacuate_failure(webob.exc.HTTPConflict,
{'host': 'my-host',
'onSharedStorage': 'False',
'adminPass': 'MyNewPass'})

View File

@@ -103,7 +103,7 @@ class EvacuatePolicyTest(base.BasePolicyTest):
evacuate_mock.assert_called_once_with( evacuate_mock.assert_called_once_with(
self.user_req.environ['nova.context'], self.user_req.environ['nova.context'],
mock.ANY, 'my-host', False, mock.ANY, 'my-host', False,
'MyNewPass', None) 'MyNewPass', None, None)
class EvacuateNoLegacyNoScopePolicyTest(EvacuatePolicyTest): class EvacuateNoLegacyNoScopePolicyTest(EvacuatePolicyTest):

View File

@@ -0,0 +1,13 @@
---
features:
- |
Starting with v2.95 any evacuated instance will be stopped at
destination. The required minimum version for Nova computes is
27.0.0 (antelope 2023.1). Operator can still continue using
previous behavior by selecting microversion below v2.95.
upgrade:
- |
Operators will have to consider upgrading compute hosts to Nova
27.0.0 (antelope 2023.1) in order to take advantage of the new
(microversion v2.95) evacuate API behavior. An exception will be
raised for older versions.