summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2019-04-22 05:25:06 +0000
committerGerrit Code Review <review@openstack.org>2019-04-22 05:25:06 +0000
commit6e6ce5135f3dd989b8051905cbd800ab2855a1b6 (patch)
tree48846a84134c0fd849c2783325264623b4572719
parent7881a9c8d4893fe0411bd52142237d37bd4db249 (diff)
parentd6832b0e070cebbe48a71a46d88b9412375989c1 (diff)
downloadnova-6e6ce5135f3dd989b8051905cbd800ab2855a1b6.tar.gz
Merge "Update instance.availability_zone during live migration" into stable/pike
-rw-r--r--nova/conductor/tasks/live_migrate.py5
-rw-r--r--nova/tests/functional/test_availability_zones.py131
-rw-r--r--nova/tests/unit/conductor/tasks/test_live_migrate.py9
3 files changed, 143 insertions, 2 deletions
diff --git a/nova/conductor/tasks/live_migrate.py b/nova/conductor/tasks/live_migrate.py
index a5b1ddfde9..0d7dce6962 100644
--- a/nova/conductor/tasks/live_migrate.py
+++ b/nova/conductor/tasks/live_migrate.py
@@ -14,6 +14,7 @@ from oslo_log import log as logging
import oslo_messaging as messaging
import six
+from nova import availability_zones
from nova.compute import power_state
from nova.conductor.tasks import base
import nova.conf
@@ -77,6 +78,10 @@ class LiveMigrationTask(base.TaskBase):
self.scheduler_client.reportclient, self.instance,
source_node, dest_node)
+ self.instance.availability_zone = (
+ availability_zones.get_host_availability_zone(
+ self.context, self.destination))
+
# TODO(johngarbutt) need to move complexity out of compute manager
# TODO(johngarbutt) disk_over_commit?
return self.compute_rpcapi.live_migration(self.context,
diff --git a/nova/tests/functional/test_availability_zones.py b/nova/tests/functional/test_availability_zones.py
new file mode 100644
index 0000000000..11a566a813
--- /dev/null
+++ b/nova/tests/functional/test_availability_zones.py
@@ -0,0 +1,131 @@
+# 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 import test
+from nova.tests import fixtures as nova_fixtures
+from nova.tests.functional import integrated_helpers
+from nova.tests.unit.image import fake as fake_image
+from nova.tests.unit import policy_fixture
+from nova.virt import fake
+
+
+class TestAvailabilityZoneScheduling(
+ test.TestCase, integrated_helpers.InstanceHelperMixin):
+
+ def setUp(self):
+ super(TestAvailabilityZoneScheduling, 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 = 'latest'
+
+ fake_image.stub_out_image_service(self)
+ self.addCleanup(fake_image.FakeImageService_reset)
+
+ self.start_service('conductor')
+ self.start_service('scheduler')
+
+ def _start_host_in_zone(self, host, zone):
+ # Start the nova-compute service.
+ fake.set_nodes([host])
+ self.addCleanup(fake.restore_nodes)
+ self.start_service('compute', host=host)
+ # Create a host aggregate with a zone in which to put this host.
+ aggregate_body = {
+ "aggregate": {
+ "name": zone,
+ "availability_zone": zone
+ }
+ }
+ aggregate = self.api.api_post(
+ '/os-aggregates', aggregate_body).body['aggregate']
+ # Now add the compute host to the aggregate.
+ add_host_body = {
+ "add_host": {
+ "host": host
+ }
+ }
+ self.api.api_post(
+ '/os-aggregates/%s/action' % aggregate['id'], add_host_body)
+
+ def test_live_migrate_implicit_az(self):
+ """Tests live migration of an instance with an implicit AZ.
+
+ Before Pike, a server created without an explicit availability zone
+ was assigned a default AZ based on the "default_schedule_zone" config
+ option which defaults to None, which allows the instance to move
+ freely between availability zones.
+
+ With change I8d426f2635232ffc4b510548a905794ca88d7f99 in Pike, if the
+ user does not request an availability zone, the
+ instance.availability_zone field is set based on the host chosen by
+ the scheduler. The default AZ for all nova-compute services is
+ determined by the "default_availability_zone" config option which
+ defaults to "nova".
+
+ This test creates two nova-compute services in separate zones, creates
+ a server without specifying an explicit zone, and then tries to live
+ migrate the instance to the other compute which should succeed because
+ the request spec does not include an explicit AZ, so the instance is
+ still not restricted to its current zone even if it says it is in one.
+ """
+ # Start two compute services in separate zones.
+ self._start_host_in_zone('host1', 'zone1')
+ self._start_host_in_zone('host2', 'zone2')
+
+ # Create a server, it doesn't matter which host it ends up in.
+ server_body = self._build_minimal_create_server_request(
+ self.api, 'test_live_migrate_implicit_az_restriction',
+ image_uuid=fake_image.get_valid_image_id(),
+ networks='none')
+ server = self.api.post_server({'server': server_body})
+ server = self._wait_for_state_change(self.api, server, 'ACTIVE')
+ original_host = server['OS-EXT-SRV-ATTR:host']
+ # Assert the server has the AZ set (not None or 'nova').
+ expected_zone = 'zone1' if original_host == 'host1' else 'zone2'
+ self.assertEqual(expected_zone, server['OS-EXT-AZ:availability_zone'])
+
+ # Attempt to live migrate the instance; again, we don't specify a host
+ # because there are only two hosts so the scheduler would only be able
+ # to pick the second host which is in a different zone.
+ live_migrate_req = {
+ 'os-migrateLive': {
+ 'block_migration': 'auto',
+ 'host': None
+ }
+ }
+ self.api.post_server_action(server['id'], live_migrate_req)
+
+ # Poll the migration until it is done.
+ migration = self._wait_for_migration_status(server, 'completed')
+ self.assertEqual('live-migration', migration['migration_type'])
+
+ # Assert that the server did move. Note that we check both the API and
+ # the database because the API will return the AZ from the host
+ # aggregate if instance.host is not None.
+ server = self.api.get_server(server['id'])
+ expected_zone = 'zone2' if original_host == 'host1' else 'zone1'
+ self.assertEqual(expected_zone, server['OS-EXT-AZ:availability_zone'])
+
+ ctxt = context.get_admin_context()
+ with context.target_cell(
+ ctxt, self.cell_mappings[test.CELL1_NAME]) as cctxt:
+ instance = objects.Instance.get_by_uuid(cctxt, server['id'])
+ self.assertEqual(expected_zone, instance.availability_zone)
diff --git a/nova/tests/unit/conductor/tasks/test_live_migrate.py b/nova/tests/unit/conductor/tasks/test_live_migrate.py
index a75709fa46..c004056ddb 100644
--- a/nova/tests/unit/conductor/tasks/test_live_migrate.py
+++ b/nova/tests/unit/conductor/tasks/test_live_migrate.py
@@ -60,7 +60,9 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
servicegroup.API(), scheduler_client.SchedulerClient(),
self.fake_spec)
- def test_execute_with_destination(self):
+ @mock.patch('nova.availability_zones.get_host_availability_zone',
+ return_value='fake-az')
+ def test_execute_with_destination(self, mock_get_az):
with test.nested(
mock.patch.object(self.task, '_check_host_is_up'),
mock.patch.object(self.task, '_check_requested_destination',
@@ -87,7 +89,9 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
migration=self.migration,
migrate_data=None)
- def test_execute_without_destination(self):
+ @mock.patch('nova.availability_zones.get_host_availability_zone',
+ return_value='nova')
+ def test_execute_without_destination(self, mock_get_az):
self.destination = None
self._generate_task()
self.assertIsNone(self.task.destination)
@@ -112,6 +116,7 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
migration=self.migration,
migrate_data=None)
self.assertTrue(mock_save.called)
+ mock_get_az.assert_called_once_with(self.context, 'found_host')
self.assertEqual('found_host', self.migration.dest_compute)
def test_check_instance_is_active_passes_when_paused(self):