diff --git a/nova/api/openstack/compute/schemas/servers.py b/nova/api/openstack/compute/schemas/servers.py index 72db5d6a91..1db28bb172 100644 --- a/nova/api/openstack/compute/schemas/servers.py +++ b/nova/api/openstack/compute/schemas/servers.py @@ -1392,6 +1392,53 @@ show_response_v2100['properties']['server'] = { 'oneOf': [_server_response_v2100, _server_cell_down_response_v271], } +create_response = { + 'type': 'object', + 'oneOf': [ + { + 'properties': { + 'reservation_id': {'type': 'string'}, + }, + 'required': ['reservation_id'], + 'additionalProperties': False, + }, + { + 'properties': { + 'server': { + 'type': 'object', + 'properties': { + 'adminPass': {'type': 'string'}, + 'id': {'type': 'string', 'format': 'uuid'}, + 'links': response_types.links, + 'security_groups': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + }, + 'required': ['name'], + 'additionalProperties': False, + }, + }, + 'OS-DCF:diskConfig': { + 'type': 'string', 'enum': ['AUTO', 'MANUAL'], + }, + }, + 'required': [ + # adminPass is an unfortunate example of config-driven + # API behavior and isn't present unless enabled + 'id', 'links', 'security_groups', 'OS-DCF:diskConfig' + ], + 'additionalProperties': False, + }, + }, + 'required': ['server'], + 'additionalProperties': False, + }, + ], +} + resize_response = {'type': 'null'} confirm_resize_response = {'type': 'null'} diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index e95b88316a..5c69c9565d 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -707,6 +707,7 @@ class ServersController(wsgi.Controller): @validation.schema(schema.create_v274, '2.74', '2.89') @validation.schema(schema.create_v290, '2.90', '2.93') @validation.schema(schema.create_v294, '2.94') + @validation.response_body_schema(schema.create_response) def create(self, req, body): """Creates a new server for a given user.""" context = req.environ['nova.context'] diff --git a/nova/tests/unit/policies/test_servers.py b/nova/tests/unit/policies/test_servers.py index 285516bcb0..87fae521f5 100644 --- a/nova/tests/unit/policies/test_servers.py +++ b/nova/tests/unit/policies/test_servers.py @@ -40,6 +40,14 @@ from nova.tests.unit.policies import base CONF = nova.conf.CONF +def fake_add_security_grps( + req, servers, instances, create_request=False, +): + # just enough to satisfy schema checks + if create_request: + servers[0]['security_groups'] = [{'name': 'default'}] + + class ServersPolicyTest(base.BasePolicyTest): """Test Servers APIs policies with all possible context. This class defines the set of context with different roles @@ -61,7 +69,9 @@ class ServersPolicyTest(base.BasePolicyTest): self.req = fakes.HTTPRequest.blank('') user_id = self.req.environ['nova.context'].user_id - self.controller._view_builder._add_security_grps = mock.MagicMock() + self.controller._view_builder._add_security_grps = mock.MagicMock( + side_effect=fake_add_security_grps + ) self.controller._view_builder._get_metadata = mock.MagicMock() self.controller._view_builder._get_addresses = mock.MagicMock() self.controller._view_builder._get_host_id = mock.MagicMock( @@ -81,7 +91,7 @@ class ServersPolicyTest(base.BasePolicyTest): fixtures.MockPatch('nova.compute.flavors.get_flavor_by_flavor_id') ).mock self.mock_flavor.return_value = fake_flavor.fake_flavor_obj( - self.req.environ['nova.context'], flavorid='1') + self.req.environ['nova.context'], flavorid='1') self.mock_get = self.useFixture( fixtures.MockPatch('nova.api.openstack.common.get_instance')).mock