diff --git a/nova/tests/functional/test_aggregates.py b/nova/tests/functional/test_aggregates.py index 59137929af..8be7ded9e3 100644 --- a/nova/tests/functional/test_aggregates.py +++ b/nova/tests/functional/test_aggregates.py @@ -551,3 +551,123 @@ class TestAggregateMultiTenancyIsolationFilter( server = user_api.post_server(server_req) self._wait_for_state_change(user_api, server, 'ACTIVE') self.assertEqual(2, len(self.filtered_hosts)) + + +class AggregateMultiTenancyIsolationColdMigrateTest( + test.TestCase, integrated_helpers.InstanceHelperMixin): + + @staticmethod + def _create_aggregate(admin_api, name): + return admin_api.api_post( + '/os-aggregates', {'aggregate': {'name': name}}).body['aggregate'] + + @staticmethod + def _add_host_to_aggregate(admin_api, aggregate, host): + add_host_req_body = { + "add_host": { + "host": host + } + } + admin_api.api_post( + '/os-aggregates/%s/action' % aggregate['id'], add_host_req_body) + + @staticmethod + def _isolate_aggregate(admin_api, aggregate, tenant_id): + set_meta_req_body = { + "set_metadata": { + "metadata": { + "filter_tenant_id": tenant_id + } + } + } + admin_api.api_post( + '/os-aggregates/%s/action' % aggregate['id'], set_meta_req_body) + + def setUp(self): + super(AggregateMultiTenancyIsolationColdMigrateTest, self).setUp() + self.useFixture(policy_fixture.RealPolicyFixture()) + self.useFixture(nova_fixtures.NeutronFixture(self)) + self.useFixture(func_fixtures.PlacementFixture()) + # Intentionally keep these separate since we want to create the + # server with the non-admin user in a different project. + admin_api_fixture = self.useFixture(nova_fixtures.OSAPIFixture( + api_version='v2.1', project_id=uuids.admin_project)) + self.admin_api = admin_api_fixture.admin_api + self.admin_api.microversion = 'latest' + user_api_fixture = self.useFixture(nova_fixtures.OSAPIFixture( + api_version='v2.1', project_id=uuids.user_project)) + self.api = user_api_fixture.api + self.api.microversion = 'latest' + + # the image fake backend needed for image discovery + nova.tests.unit.image.fake.stub_out_image_service(self) + self.addCleanup(nova.tests.unit.image.fake.FakeImageService_reset) + + self.start_service('conductor') + # Enable the AggregateMultiTenancyIsolation filter before starting the + # scheduler service. + enabled_filters = CONF.filter_scheduler.enabled_filters + if 'AggregateMultiTenancyIsolation' not in enabled_filters: + enabled_filters.append('AggregateMultiTenancyIsolation') + self.flags( + enabled_filters=enabled_filters, group='filter_scheduler') + # Add a custom weigher which will weigh host1, which will be in the + # admin project aggregate, higher than the other hosts which are in + # the non-admin project aggregate. + self.flags(weight_classes=[__name__ + '.HostNameWeigher'], + group='filter_scheduler') + self.start_service('scheduler') + + for host in ('host1', 'host2', 'host3'): + self.start_service('compute', host=host) + + # Create an admin-only aggregate for the admin project. This is needed + # because if host1 is not in an aggregate with the filter_tenant_id + # metadata key, the filter will accept that host even for the non-admin + # project. + admin_aggregate = self._create_aggregate( + self.admin_api, 'admin-aggregate') + self._add_host_to_aggregate(self.admin_api, admin_aggregate, 'host1') + + # Restrict the admin project to the admin aggregate. + self._isolate_aggregate( + self.admin_api, admin_aggregate, uuids.admin_project) + + # Create the tenant aggregate for the non-admin project. + tenant_aggregate = self._create_aggregate( + self.admin_api, 'tenant-aggregate') + + # Add two compute hosts to the tenant aggregate. We exclude host1 + # since that is weighed higher in HostNameWeigher and we want to + # ensure the scheduler properly filters out host1 before we even get + # to weighing the selected hosts. + for host in ('host2', 'host3'): + self._add_host_to_aggregate(self.admin_api, tenant_aggregate, host) + + # Restrict the non-admin project to the tenant aggregate. + self._isolate_aggregate( + self.admin_api, tenant_aggregate, uuids.user_project) + + def test_cold_migrate_server(self): + """Creates a server using the non-admin project, then cold migrates + the server and asserts the server goes to the other host in the + isolated host aggregate via the AggregateMultiTenancyIsolation filter. + """ + img = nova.tests.unit.image.fake.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID + server_req_body = self._build_minimal_create_server_request( + self.api, 'test_cold_migrate_server', image_uuid=img, + networks='none') + server = self.api.post_server({'server': server_req_body}) + server = self._wait_for_state_change(self.admin_api, server, 'ACTIVE') + # Ensure the server ended up in host2 or host3 + original_host = server['OS-EXT-SRV-ATTR:host'] + self.assertNotEqual('host1', original_host) + # Now cold migrate the server and it should end up in the other host + # in the same tenant-isolated aggregate. + self.admin_api.api_post( + '/servers/%s/action' % server['id'], {'migrate': None}) + server = self._wait_for_state_change( + self.admin_api, server, 'VERIFY_RESIZE') + # Ensure the server is on the other host in the same aggregate. + expected_host = 'host3' if original_host == 'host2' else 'host2' + self.assertEqual(expected_host, server['OS-EXT-SRV-ATTR:host'])