diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index b5d62a6979..a49b8ad5e3 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -4806,7 +4806,8 @@ migrate_dest_host: type: string migrate_dest_node: description: | - The target node for a migration. + The target node for a migration. This will be ``null`` if pre-migration + checks fail due to e.g. insufficient resources. in: body required: true type: string @@ -4854,7 +4855,8 @@ migrate_source_compute: type: string migrate_source_node: description: | - The source node for a migration. + The source node for a migration. This will be ``null`` if pre-migration + checks fail due to e.g. insufficient resources. in: body required: true type: string diff --git a/nova/api/openstack/compute/migrations.py b/nova/api/openstack/compute/migrations.py index 6baf8447bc..8904bf7da9 100644 --- a/nova/api/openstack/compute/migrations.py +++ b/nova/api/openstack/compute/migrations.py @@ -27,6 +27,7 @@ from nova.objects import fields from nova.policies import migrations as migrations_policies +@validation.validated class MigrationsController(wsgi.Controller): """Controller for accessing migrations in OpenStack API.""" @@ -179,11 +180,14 @@ class MigrationsController(wsgi.Controller): @wsgi.expected_errors((), "2.1", "2.58") @wsgi.expected_errors(400, "2.59") - @validation.query_schema(schema.list_query_schema_v20, "2.0", "2.22") - @validation.query_schema(schema.list_query_schema_v20, "2.23", "2.58") - @validation.query_schema(schema.list_query_params_v259, "2.59", "2.65") - @validation.query_schema(schema.list_query_params_v266, "2.66", "2.79") - @validation.query_schema(schema.list_query_params_v280, "2.80") + @validation.query_schema(schema.index_query_v20, "2.0", "2.58") + @validation.query_schema(schema.index_query_v259, "2.59", "2.65") + @validation.query_schema(schema.index_query_v266, "2.66", "2.79") + @validation.query_schema(schema.index_query_v280, "2.80") + @validation.response_body_schema(schema.index_response_v20, "2.0", "2.22") + @validation.response_body_schema(schema.index_response_v223, "2.23", "2.58") # noqa: E501 + @validation.response_body_schema(schema.index_response_v259, "2.59", "2.79") # noqa: E501 + @validation.response_body_schema(schema.index_response_v280, "2.80") def index(self, req): """Return all migrations using the query parameters as filters.""" add_link = False diff --git a/nova/api/openstack/compute/schemas/migrations.py b/nova/api/openstack/compute/schemas/migrations.py index 0979e58c9d..e5be502910 100644 --- a/nova/api/openstack/compute/schemas/migrations.py +++ b/nova/api/openstack/compute/schemas/migrations.py @@ -15,8 +15,10 @@ import copy from nova.api.validation import parameter_types +from nova.api.validation import response_types -list_query_schema_v20 = { + +index_query_v20 = { 'type': 'object', 'properties': { 'hidden': parameter_types.common_query_param, @@ -30,8 +32,8 @@ list_query_schema_v20 = { 'additionalProperties': True } -list_query_params_v259 = copy.deepcopy(list_query_schema_v20) -list_query_params_v259['properties'].update({ +index_query_v259 = copy.deepcopy(index_query_v20) +index_query_v259['properties'].update({ # The 2.59 microversion added support for paging by limit and marker # and filtering by changes-since. 'limit': parameter_types.single_param( @@ -40,18 +42,97 @@ list_query_params_v259['properties'].update({ 'changes-since': parameter_types.single_param( {'type': 'string', 'format': 'date-time'}), }) -list_query_params_v259['additionalProperties'] = False +index_query_v259['additionalProperties'] = False -list_query_params_v266 = copy.deepcopy(list_query_params_v259) -list_query_params_v266['properties'].update({ +index_query_v266 = copy.deepcopy(index_query_v259) +index_query_v266['properties'].update({ 'changes-before': parameter_types.single_param( {'type': 'string', 'format': 'date-time'}), }) -list_query_params_v280 = copy.deepcopy(list_query_params_v266) -list_query_params_v280['properties'].update({ +index_query_v280 = copy.deepcopy(index_query_v266) +index_query_v280['properties'].update({ # The 2.80 microversion added support for filtering migrations # by user_id and/or project_id 'user_id': parameter_types.single_param({'type': 'string'}), 'project_id': parameter_types.single_param({'type': 'string'}), }) + +index_response_v20 = { + 'type': 'object', + 'properties': { + 'migrations': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'created_at': {'type': 'string', 'format': 'date-time'}, + 'dest_compute': {'type': ['string', 'null']}, + 'dest_host': {'type': ['string', 'null']}, + 'dest_node': {'type': ['string', 'null']}, + 'id': {'type': 'integer'}, + 'instance_uuid': {'type': 'string', 'format': 'uuid'}, + 'new_instance_type_id': {'type': ['integer', 'null']}, + 'old_instance_type_id': {'type': ['integer', 'null']}, + 'source_compute': {'type': ['string', 'null']}, + 'source_node': {'type': ['string', 'null']}, + 'status': {'type': 'string'}, + 'updated_at': { + 'type': ['string', 'null'], 'format': 'date-time' + }, + }, + 'required': [ + 'created_at', + 'dest_compute', + 'dest_host', + 'dest_node', + 'id', + 'instance_uuid', + 'new_instance_type_id', + 'old_instance_type_id', + 'source_compute', + 'source_node', + 'status', + 'updated_at', + ], + 'additionalProperties': False, + }, + }, + }, + 'required': ['migrations'], + 'additionalProperties': False, +} + +index_response_v223 = copy.deepcopy(index_response_v20) +index_response_v223['properties']['migrations']['items']['properties'].update({ + 'migration_type': { + 'type': 'string', + 'enum': [ + 'migration', 'resize', 'live-migration', 'evacuation' + ], + }, + 'links': response_types.links, +}) +index_response_v223['properties']['migrations']['items']['required'].append( + 'migration_type' +) + +index_response_v259 = copy.deepcopy(index_response_v223) +index_response_v259['properties'].update({ + 'migrations_links': response_types.collection_links, +}) +index_response_v259['properties']['migrations']['items']['properties'].update({ + 'uuid': {'type': 'string', 'format': 'uuid'}, +}) +index_response_v259['properties']['migrations']['items']['required'].append( + 'uuid' +) + +index_response_v280 = copy.deepcopy(index_response_v259) +index_response_v280['properties']['migrations']['items']['properties'].update({ + 'project_id': parameter_types.project_id, + 'user_id': parameter_types.user_id, +}) +index_response_v280['properties']['migrations']['items']['required'].extend([ + 'project_id', 'user_id', +]) diff --git a/nova/tests/unit/api/openstack/compute/test_migrations.py b/nova/tests/unit/api/openstack/compute/test_migrations.py index 53fe3fb915..d7b7940089 100644 --- a/nova/tests/unit/api/openstack/compute/test_migrations.py +++ b/nova/tests/unit/api/openstack/compute/test_migrations.py @@ -55,8 +55,8 @@ fake_migrations = [ 'deleted': False, 'uuid': uuids.migration1, 'cross_cell_move': False, - 'user_id': None, - 'project_id': None + 'user_id': uuids.user_id, + 'project_id': uuids.project_id, }, # non in-progress live migration { @@ -85,8 +85,8 @@ fake_migrations = [ 'deleted': False, 'uuid': uuids.migration2, 'cross_cell_move': False, - 'user_id': None, - 'project_id': None + 'user_id': uuids.user_id, + 'project_id': uuids.project_id, }, # in-progress resize { @@ -115,8 +115,8 @@ fake_migrations = [ 'deleted': False, 'uuid': uuids.migration3, 'cross_cell_move': False, - 'user_id': None, - 'project_id': None + 'user_id': uuids.user_id, + 'project_id': uuids.project_id, }, # non in-progress resize { @@ -145,8 +145,8 @@ fake_migrations = [ 'deleted': False, 'uuid': uuids.migration4, 'cross_cell_move': False, - 'user_id': None, - 'project_id': None + 'user_id': uuids.user_id, + 'project_id': uuids.project_id, } ] diff --git a/nova/tests/unit/policies/test_migrations.py b/nova/tests/unit/policies/test_migrations.py index b2cea1f463..db8de35acf 100644 --- a/nova/tests/unit/policies/test_migrations.py +++ b/nova/tests/unit/policies/test_migrations.py @@ -64,7 +64,7 @@ class MigrationsPolicyTest(base.BasePolicyTest): 'dest_compute': 'compute2', 'dest_host': '1.2.3.4', 'status': 'running', - 'instance_uuid': 1234, + 'instance_uuid': uuids.instance, 'old_instance_type_id': 1, 'new_instance_type_id': 2, 'migration_type': 'migration', @@ -81,8 +81,8 @@ class MigrationsPolicyTest(base.BasePolicyTest): 'deleted': False, 'uuid': uuids.migration1, 'cross_cell_move': False, - 'user_id': None, - 'project_id': 'other_project' + 'user_id': uuids.user_id, + 'project_id': uuids.other_project_id, } ] if project_id is None: @@ -239,17 +239,18 @@ class MigrationsPolicyTest(base.BasePolicyTest): # NOTE(gmaan): Only Admin (not Project manager) can list the # other project migrations. for auth_cxtx in self.project_admin_authorized_contexts: - project_id = 'other_project' + project_id = uuids.other_project_id req, _ = self.prepare_microversion_request( auth_cxtx, mock_get, project_id=project_id) resp = self.controller.index(req) # NOTE(gmaan): Check their own project migrations are returned self.assertEqual(1, len(resp['migrations'])) - self.assertEqual('other_project', + self.assertEqual(project_id, resp['migrations'][0]['project_id']) + for unauth_cxtx in (self.all_contexts - set(self.project_admin_authorized_contexts)): - project_id = 'other_project' + project_id = uuids.other_project_id req, _ = self.prepare_microversion_request( unauth_cxtx, mock_get, project_id=project_id) exc = self.assertRaises(