Add functional regression test for bug 1669054

Change Ic3968721d257a167f3f946e5387cd227a7eeec6c in Newton
started setting the RequestSpec.ignore_hosts field to the
source instance.host during resize/cold migrate if
allow_resize_to_same_host=False in config, which it is by
default.

Change I8abdf58a6537dd5e15a012ea37a7b48abd726579 also in
Newton persists changes to the RequestSpec in conductor
in order to save the RequestSpec.flavor for the new flavor.
This inadvertently persists the ignore_hosts field as well.

Later if you try to evacuate or unshelve the server it will ignore
the original source host because of the persisted ignore_hosts value.
This is obviously a problem in a small deployment with only a few
compute nodes (like an edge deployment). As a result, an evacuation
can fail if the only available host is the one being ignored.

This adds a functional regression recreate test for the bug.

NOTE(mriedem): This backport differs slightly in that 204 is added
to the default check_response_status POST call which was added in
Queens change I6a51542216340299d250576714e303f74e0ceb0f.

Change-Id: I6ce2d6b1baf47796f867aede1acf292ec9739d6d
Related-Bug: #1669054
(cherry picked from commit 556cf103b2)
(cherry picked from commit 20c1414945)
(cherry picked from commit 77164128bf)
(cherry picked from commit 9fd4082d7c)
This commit is contained in:
Matt Riedemann 2019-03-23 12:01:42 -04:00
parent 1a2e761b54
commit fcd718dcdd
2 changed files with 89 additions and 1 deletions

View File

@ -230,7 +230,7 @@ class TestOpenStackClient(object):
headers['Content-Type'] = 'application/json'
kwargs['body'] = jsonutils.dumps(body)
kwargs.setdefault('check_response_status', [200, 201, 202])
kwargs.setdefault('check_response_status', [200, 201, 202, 204])
return APIResponse(self.api_request(relative_uri, **kwargs))
def api_put(self, relative_uri, body, **kwargs):

View File

@ -0,0 +1,88 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import context
from nova import objects
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_network
from nova.virt import fake
class ResizeEvacuateTestCase(integrated_helpers._IntegratedTestBase,
integrated_helpers.InstanceHelperMixin):
"""Regression test for bug 1669054 introduced in Newton.
When resizing a server, if CONF.allow_resize_to_same_host is False,
the API will set RequestSpec.ignore_hosts = [instance.host] and then
later in conductor the RequestSpec changes are saved to persist the new
flavor. This inadvertently saves the ignore_hosts value. Later if you
try to migrate, evacuate or unshelve the server, that original source
host will be ignored. If the deployment has a small number of computes,
like two in an edge node, then evacuate will fail because the only other
available host is ignored. This test recreates the scenario.
"""
# Set variables used in the parent class.
REQUIRES_LOCKING = False
ADMIN_API = True
USE_NEUTRON = True
_image_ref_parameter = 'imageRef'
_flavor_ref_parameter = 'flavorRef'
api_major_version = 'v2.1'
microversion = '2.11' # Need at least 2.11 for the force-down API
def setUp(self):
super(ResizeEvacuateTestCase, self).setUp()
fake_network.set_stub_network_methods(self)
def test_resize_then_evacuate(self):
# Create a server. At this point there is only one compute service.
flavors = self.api.get_flavors()
flavor1 = flavors[0]['id']
server = self._build_server(flavor1)
server = self.api.post_server({'server': server})
self._wait_for_state_change(self.api, server, 'ACTIVE')
# Start up another compute service so we can resize.
fake.set_nodes(['host2'])
self.addCleanup(fake.restore_nodes)
host2 = self.start_service('compute', host='host2')
# Now resize the server to move it to host2.
flavor2 = flavors[1]['id']
req = {'resize': {'flavorRef': flavor2}}
self.api.post_server_action(server['id'], req)
server = self._wait_for_state_change(self.api, server, 'VERIFY_RESIZE')
self.assertEqual('host2', server['OS-EXT-SRV-ATTR:host'])
self.api.post_server_action(server['id'], {'confirmResize': None})
server = self._wait_for_state_change(self.api, server, 'ACTIVE')
# Disable the host on which the server is now running (host2).
host2.stop()
self.api.force_down_service('host2', 'nova-compute', forced_down=True)
# Now try to evacuate the server back to the original source compute.
# FIXME(mriedem): This is bug 1669054 where the evacuate fails with
# NoValidHost because the RequestSpec.ignore_hosts field has the
# original source host in it which is the only other available host to
# which we can evacuate the server.
req = {'evacuate': {'onSharedStorage': False}}
self.api.post_server_action(server['id'], req,
check_response_status=[500])
# There should be fault recorded with the server.
server = self._wait_for_state_change(self.api, server, 'ERROR')
self.assertIn('fault', server)
self.assertIn('No valid host was found', server['fault']['message'])
# Assert the RequestSpec.ignore_hosts is still populated.
reqspec = objects.RequestSpec.get_by_instance_uuid(
context.get_admin_context(), server['id'])
self.assertIsNotNone(reqspec.ignore_hosts)
self.assertIn(self.compute.host, reqspec.ignore_hosts)