From e87d9f51abea0b1c97628ef02435157da4b334ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?El=C5=91d=20Ill=C3=A9s?= Date: Fri, 1 Sep 2017 15:36:20 +0200 Subject: [PATCH] Functional test for regression bug #1713783 Add functional test for evacuation, when no valid host available. Migration should end up in 'error' state. Change-Id: I1adc20f2a5261e6906a18b9aee5cd2c8ecf0cf4d Related-bug: #1713783 (cherry picked from commit 5687c170ea003aafa2720fb00af9d84836bb41df) --- nova/tests/functional/api/client.py | 3 + .../regressions/test_bug_1713783.py | 124 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 nova/tests/functional/regressions/test_bug_1713783.py diff --git a/nova/tests/functional/api/client.py b/nova/tests/functional/api/client.py index 8657f60952bb..d26ac1c36eeb 100644 --- a/nova/tests/functional/api/client.py +++ b/nova/tests/functional/api/client.py @@ -443,3 +443,6 @@ class TestOpenStackClient(object): def get_active_migrations(self, server_id): return self.api_get('/servers/%s/migrations' % server_id).body['migrations'] + + def get_migrations(self): + return self.api_get('os-migrations').body['migrations'] diff --git a/nova/tests/functional/regressions/test_bug_1713783.py b/nova/tests/functional/regressions/test_bug_1713783.py new file mode 100644 index 000000000000..4da5c6086497 --- /dev/null +++ b/nova/tests/functional/regressions/test_bug_1713783.py @@ -0,0 +1,124 @@ +# Copyright 2017 Ericsson +# +# 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. + +import time + +from oslo_log import log as logging + +from nova import test +from nova.tests import fixtures as nova_fixtures +from nova.tests.functional import integrated_helpers +from nova.tests.unit import fake_network +from nova.tests.unit import fake_notifier +import nova.tests.unit.image.fake +from nova.tests.unit import policy_fixture + + +LOG = logging.getLogger(__name__) + + +class FailedEvacuateStateTests(test.TestCase, + integrated_helpers.InstanceHelperMixin): + """Regression Tests for bug #1713783 + + When evacuation fails with NoValidHost, the migration status remains + 'accepted' instead of 'error'. This causes problem in case the compute + service starts up again and looks for migrations with status 'accepted', + as it then removes the local instances for those migrations even though + the instance never actually migrated to another host. + """ + + microversion = 'latest' + + def setUp(self): + super(FailedEvacuateStateTests, self).setUp() + + self.useFixture(policy_fixture.RealPolicyFixture()) + self.useFixture(nova_fixtures.NeutronFixture(self)) + self.useFixture(nova_fixtures.PlacementFixture()) + + api_fixture = self.useFixture(nova_fixtures.OSAPIFixture( + api_version='v2.1')) + + self.api = api_fixture.admin_api + self.api.microversion = self.microversion + + nova.tests.unit.image.fake.stub_out_image_service(self) + + self.start_service('conductor') + self.start_service('scheduler') + + self.addCleanup(nova.tests.unit.image.fake.FakeImageService_reset) + + self.hostname = 'host1' + self.compute1 = self.start_service('compute', host=self.hostname) + fake_network.set_stub_network_methods(self) + + flavors = self.api.get_flavors() + self.flavor1 = flavors[0] + + def _wait_for_notification_event_type(self, event_type, max_retries=10): + retry_counter = 0 + while True: + if len(fake_notifier.NOTIFICATIONS) > 0: + for notification in fake_notifier.NOTIFICATIONS: + if notification.event_type == event_type: + return + if retry_counter == max_retries: + self.fail('Wait for notification event type (%s) failed' + % event_type) + retry_counter += 1 + time.sleep(0.5) + + def _boot_a_server(self): + server_req = self._build_minimal_create_server_request( + self.api, 'some-server', flavor_id=self.flavor1['id'], + image_uuid='155d900f-4e14-4e4c-a73d-069cbf4541e6', + networks='none') + LOG.info('booting on %s', self.hostname) + created_server = self.api.post_server({'server': server_req}) + return self._wait_for_state_change( + self.api, created_server, 'ACTIVE') + + def test_evacuate_no_valid_host(self): + # Boot a server + server = self._boot_a_server() + + # Force source compute down + compute_id = self.api.get_services( + host=self.hostname, binary='nova-compute')[0]['id'] + self.api.put_service(compute_id, {'forced_down': 'true'}) + + fake_notifier.stub_notifier(self) + fake_notifier.reset() + + # Initiate evacuation + post = {'evacuate': {}} + self.api.post_server_action(server['id'], post) + + self._wait_for_notification_event_type('compute_task.rebuild_server') + + server = self._wait_for_state_change(self.api, server, 'ACTIVE') + self.assertEqual(self.hostname, server['OS-EXT-SRV-ATTR:host']) + + # Check migrations + migrations = self.api.get_migrations() + self.assertEqual(1, len(migrations)) + self.assertEqual('evacuation', migrations[0]['migration_type']) + self.assertEqual(server['id'], migrations[0]['instance_uuid']) + self.assertEqual(self.hostname, migrations[0]['source_compute']) + self.assertEqual('accepted', migrations[0]['status']) + # NOTE(elod.illes): Migration status should be 'error' and not + # 'accepted'. Needs to be replaced when bug #1713783 is fixed. + # self.assertEqual('error', migrations[0]['status'])