diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index 7567ce18..77c8b883 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -586,6 +586,19 @@ provision_state: in: body required: true type: string +scheduler_hints: + description: | + The dictionary of data send to the scheduler, it represents scheduling + options will be passed to scheduler. + in: body + required: false + type: object +server: + description: | + The dictionary of data represent a server creation request. + in: body + required: true + type: object server_description: description: | A free form description of the server. Limited to 255 characters diff --git a/api-ref/source/v1/samples/servers/server-create-req.json b/api-ref/source/v1/samples/servers/server-create-req.json index 0844a2fe..e6785b42 100644 --- a/api-ref/source/v1/samples/servers/server-create-req.json +++ b/api-ref/source/v1/samples/servers/server-create-req.json @@ -1,26 +1,29 @@ { - "name": "test_server", - "description": "this is a test server", - "flavor_uuid": "0607b5f3-6111-424d-ba46-f5de39a6fa69", - "image_uuid": "efe0a06f-ca95-4808-b41e-9f55b9c5eb98", - "availability_zone": "mogan", - "networks": [ - { - "net_id": "c1940655-8b8e-4370-b8f9-03ba1daeca31" + "server": { + "name": "test_server", + "description": "this is a test server", + "flavor_uuid": "0607b5f3-6111-424d-ba46-f5de39a6fa69", + "image_uuid": "efe0a06f-ca95-4808-b41e-9f55b9c5eb98", + "availability_zone": "mogan", + "networks": [ + { + "net_id": "c1940655-8b8e-4370-b8f9-03ba1daeca31" + }, + { + "net_id": "8e8ceb07-4641-4188-9b22-840755e92ee2" + } + ], + "metadata" : { + "My Server Name" : "Apache1" }, - { - "net_id": "8e8ceb07-4641-4188-9b22-840755e92ee2" - } - ], - "metadata" : { - "My Server Name" : "Apache1" + "personality": [ + { + "path": "/etc/banner.txt", + "contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ], + "user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==", + "key_name": "test_key" }, - "personality": [ - { - "path": "/etc/banner.txt", - "contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" - } - ], - "user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==", - "key_name": "test_key" + "scheduler_hints": {"group": "group1"} } diff --git a/api-ref/source/v1/servers.inc b/api-ref/source/v1/servers.inc index 5d81c40c..41941bf5 100644 --- a/api-ref/source/v1/servers.inc +++ b/api-ref/source/v1/servers.inc @@ -30,6 +30,7 @@ Request .. rest_parameters:: parameters.yaml + - server: server - name: server_name - description: server_description - flavor_uuid: flavorRef @@ -42,6 +43,7 @@ Request - user_data: user_data - personality: personality - key_name: key_name + - scheduler_hints: scheduler_hints **Example Create Server: JSON request** diff --git a/mogan/api/controllers/v1/schemas/servers.py b/mogan/api/controllers/v1/schemas/servers.py index 37602018..d3e523e8 100644 --- a/mogan/api/controllers/v1/schemas/servers.py +++ b/mogan/api/controllers/v1/schemas/servers.py @@ -16,37 +16,50 @@ from mogan.api.validation import parameter_types - create_server = { "type": "object", "properties": { - 'name': parameter_types.name, - 'description': parameter_types.description, - 'availability_zone': parameter_types.availability_zone, - 'image_uuid': parameter_types.image_id, - 'flavor_uuid': parameter_types.flavor_id, - 'networks': { - 'type': 'array', 'minItems': 1, - 'items': { - 'type': 'object', - 'properties': { - 'net_id': parameter_types.network_id, - 'port_id': parameter_types.network_port_id, + "server": { + "type": "object", + "properties": { + 'name': parameter_types.name, + 'description': parameter_types.description, + 'availability_zone': parameter_types.availability_zone, + 'image_uuid': parameter_types.image_id, + 'flavor_uuid': parameter_types.flavor_id, + 'networks': { + 'type': 'array', 'minItems': 1, + 'items': { + 'type': 'object', + 'properties': { + 'net_id': parameter_types.network_id, + 'port_id': parameter_types.network_port_id, + }, + 'oneOf': [ + {'required': ['net_id']}, + {'required': ['port_id']} + ], + }, + 'additionalProperties': False, }, - 'oneOf': [ - {'required': ['net_id']}, - {'required': ['port_id']} - ], - 'additionalProperties': False, + 'user_data': {'type': 'string', 'format': 'base64'}, + 'personality': parameter_types.personality, + 'key_name': parameter_types.name, + 'min_count': {'type': 'integer', 'minimum': 1}, + 'max_count': {'type': 'integer', 'minimum': 1}, + 'metadata': parameter_types.metadata, }, + 'required': ['name', 'image_uuid', 'flavor_uuid', 'networks'], + 'additionalProperties': False, + }, + "scheduler_hints": { + 'type': 'object', + 'properties': { + 'group': parameter_types.server_group_id + }, + 'additionalProperties': False, }, - 'user_data': {'type': 'string', 'format': 'base64'}, - 'personality': parameter_types.personality, - 'key_name': parameter_types.name, - 'min_count': {'type': 'integer', 'minimum': 1}, - 'max_count': {'type': 'integer', 'minimum': 1}, - 'metadata': parameter_types.metadata, }, - 'required': ['name', 'image_uuid', 'flavor_uuid', 'networks'], + 'required': ['server'], 'additionalProperties': False, } diff --git a/mogan/api/controllers/v1/servers.py b/mogan/api/controllers/v1/servers.py index 3744b449..10f90eda 100644 --- a/mogan/api/controllers/v1/servers.py +++ b/mogan/api/controllers/v1/servers.py @@ -678,6 +678,9 @@ class ServerController(ServerControllerBase): :param server: a server within the request body. """ validation.check_schema(server, server_schemas.create_server) + scheduler_hints = server.get('scheduler_hints', {}) + server = server.get('server') + min_count = server.get('min_count', 1) max_count = server.get('max_count', min_count) @@ -715,8 +718,8 @@ class ServerController(ServerControllerBase): injected_files=injected_files, key_name=key_name, min_count=min_count, - max_count=max_count) - + max_count=max_count, + scheduler_hints=scheduler_hints) # Set the HTTP Location Header for the first server. pecan.response.location = link.build_url('server', servers[0].uuid) return Server.convert_with_links(servers[0]) diff --git a/mogan/api/validation/parameter_types.py b/mogan/api/validation/parameter_types.py index 70746f8c..e1a57b3a 100644 --- a/mogan/api/validation/parameter_types.py +++ b/mogan/api/validation/parameter_types.py @@ -58,6 +58,9 @@ flavor_id = { 'type': 'string', 'format': 'uuid' } +server_group_id = { + 'type': 'string', 'format': 'uuid' +} metadata = { 'type': 'object', diff --git a/mogan/engine/api.py b/mogan/engine/api.py index 6e43a828..d086643d 100644 --- a/mogan/engine/api.py +++ b/mogan/engine/api.py @@ -265,7 +265,7 @@ class API(object): def _create_server(self, context, flavor, image_uuid, name, description, availability_zone, metadata, requested_networks, user_data, injected_files, - key_name, min_count, max_count): + key_name, min_count, max_count, scheduler_hints): """Verify all the input parameters""" # Verify the specified image exists @@ -306,6 +306,7 @@ class API(object): }, 'flavor': dict(flavor), 'availability_zone': availability_zone, + 'scheduler_hints': scheduler_hints } self.engine_rpcapi.schedule_and_create_servers(context, servers, @@ -321,7 +322,7 @@ class API(object): name=None, description=None, availability_zone=None, metadata=None, requested_networks=None, user_data=None, injected_files=None, key_name=None, min_count=None, - max_count=None): + max_count=None, scheduler_hints=None): """Provision servers Sending server information to the engine and will handle @@ -340,7 +341,7 @@ class API(object): availability_zone, metadata, requested_networks, user_data, injected_files, key_name, - min_count, max_count) + min_count, max_count, scheduler_hints) def _delete_server(self, context, server): diff --git a/mogan/tests/functional/api/v1/test_servers.py b/mogan/tests/functional/api/v1/test_servers.py index cca7fc43..41136f4c 100644 --- a/mogan/tests/functional/api/v1/test_servers.py +++ b/mogan/tests/functional/api/v1/test_servers.py @@ -102,14 +102,16 @@ class TestServers(v1_test.APITestV1): headers = self.gen_headers(self.context) for i in six.moves.xrange(amount): test_body = { - "name": "test_server_" + str(i), - "description": "just test server " + str(i), - 'flavor_uuid': self.FLAVOR_UUID, - 'image_uuid': 'b8f82429-3a13-4ffe-9398-4d1abdc256a8', - 'networks': [ - {'net_id': 'c1940655-8b8e-4370-b8f9-03ba1daeca31'} - ], - 'metadata': {'fake_key': 'fake_value'} + "server": { + "name": "test_server_" + str(i), + "description": "just test server " + str(i), + 'flavor_uuid': self.FLAVOR_UUID, + 'image_uuid': 'b8f82429-3a13-4ffe-9398-4d1abdc256a8', + 'networks': [ + {'net_id': 'c1940655-8b8e-4370-b8f9-03ba1daeca31'} + ], + 'metadata': {'fake_key': 'fake_value'} + } } responses.append( self.post_json('/servers', test_body, headers=headers, @@ -141,6 +143,31 @@ class TestServers(v1_test.APITestV1): self.delete('/flavors/' + self.FLAVOR_UUID, headers=headers, status=409) + @mock.patch('mogan.engine.rpcapi.EngineAPI.schedule_and_create_servers') + @mock.patch('oslo_utils.uuidutils.generate_uuid') + def test_server_post_with_scheduler_hints(self, mocked_uuid, + mock_create): + mocked_uuid.return_value = self.SERVER_UUIDS[0] + mock_create.return_value = mock.MagicMock() + body = { + "server": { + "name": "test_server_with_hints", + "description": "just test server with hints", + 'flavor_uuid': 'ff28b5a2-73e5-431c-b4b7-1b96b74bca7b', + 'image_uuid': 'b8f82429-3a13-4ffe-9398-4d1abdc256a8', + 'networks': [ + {'net_id': 'c1940655-8b8e-4370-b8f9-03ba1daeca31', + 'port_type': 'Ethernet'}], + 'metadata': {'fake_key': 'fake_value'} + }, + "scheduler_hints": {"group": 'group1'} + } + headers = self.gen_headers(self.context) + response = self.post_json('/servers', body, headers=headers, + status=201) + server = response.json + self.assertEqual('test_server_with_hints', server['name']) + def test_server_show(self): self._prepare_server(1) headers = self.gen_headers(self.context) diff --git a/mogan/tests/tempest/api/base.py b/mogan/tests/tempest/api/base.py index 245ca647..8800714e 100644 --- a/mogan/tests/tempest/api/base.py +++ b/mogan/tests/tempest/api/base.py @@ -81,11 +81,12 @@ class BaseBaremetalComputeTest(tempest.test.BaseTestCase): @classmethod def create_server(cls, wait_until_active=True): - body = {'name': data_utils.rand_name('mogan_server'), - 'description': "mogan tempest server", - 'flavor_uuid': cls.flavor, - 'image_uuid': cls.image_id, - "networks": [{"net_id": cls.net_id}] + body = {"server": {'name': data_utils.rand_name('mogan_server'), + 'description': "mogan tempest server", + 'flavor_uuid': cls.flavor, + 'image_uuid': cls.image_id, + "networks": [{"net_id": cls.net_id}] + } } resp = cls.baremetal_compute_client.create_server(**body) cls.server_ids.append(resp['uuid']) diff --git a/mogan/tests/unit/api/v1/test_server.py b/mogan/tests/unit/api/v1/test_server.py index 234271a1..7230d842 100644 --- a/mogan/tests/unit/api/v1/test_server.py +++ b/mogan/tests/unit/api/v1/test_server.py @@ -34,13 +34,15 @@ def gen_post_body(**kw): } ] return { - "name": kw.get("name", "test_server"), - "description": kw.get("description", "this is a test server"), - "flavor_uuid": kw.get( - "flavor_uuid", "0607b5f3-6111-424d-ba46-f5de39a6fa69"), - "image_uuid": kw.get( - "image_uuid", "efe0a06f-ca95-4808-b41e-9f55b9c5eb98"), - "networks": kw.get("networks", fake_networks) + "server": { + "name": kw.get("name", "test_server"), + "description": kw.get("description", "this is a test server"), + "flavor_uuid": kw.get( + "flavor_uuid", "0607b5f3-6111-424d-ba46-f5de39a6fa69"), + "image_uuid": kw.get( + "image_uuid", "efe0a06f-ca95-4808-b41e-9f55b9c5eb98"), + "networks": kw.get("networks", fake_networks) + } }