diff --git a/api-ref/source/v1/index.rst b/api-ref/source/v1/index.rst index 119d61d0..3586af49 100644 --- a/api-ref/source/v1/index.rst +++ b/api-ref/source/v1/index.rst @@ -15,3 +15,4 @@ Baremetal Compute API V1 (CURRENT) .. include:: flavor_access.inc .. include:: availability_zones.inc .. include:: aggregates.inc +.. include:: server_groups.inc diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index 64b026bb..ad876e39 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -45,6 +45,12 @@ port_ident: in: path required: true type: string +server_group_uuid_path: + description: | + The UUID of the server group. + in: path + required: true + type: string server_ident: description: | The UUID of the server. @@ -529,6 +535,42 @@ server_fault: in: body required: false type: object +server_group_members: + description: | + A list of uuids of servers which belong to this server group. + in: body + required: false + type: array +server_group_name: + description: | + The server group name. + in: body + required: true + type: string +server_group_policies: + description: | + A list of exactly one policy name to associate with the server group. The + current valid policy names are: + + - ``anti-affinity`` - servers in this group must be scheduled to + different affinity-zones. + - ``affinity`` - servers in this group must be scheduled to the same + affinity-zone. + in: body + required: true + type: array +server_group_uuid: + description: | + The UUID of the server group. + in: body + required: true + type: string +server_groups: + description: | + The list of existing server groups. + in: body + required: true + type: array server_name: description: | The server name. diff --git a/api-ref/source/v1/samples/server_groups/server-group-get-resp.json b/api-ref/source/v1/samples/server_groups/server-group-get-resp.json new file mode 100644 index 00000000..4358507f --- /dev/null +++ b/api-ref/source/v1/samples/server_groups/server-group-get-resp.json @@ -0,0 +1,22 @@ +{ + "user_id": "9851baf53c75452dad7951bca7b3dbac", + "uuid": "73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "links": [ + { + "href": "http://10.229.40.107/v1/server_groups/73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "rel": "self" + }, + { + "href": "http://10.229.40.107/server_groups/73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "rel": "bookmark" + } + ], + "created_at": "2017-08-02T09:18:24+00:00", + "updated_at": null, + "members": [], + "project_id": "b5f8b7e5429449a8a1366088abede8d1", + "policies": [ + "anti-affinity" + ], + "name": "test" +} \ No newline at end of file diff --git a/api-ref/source/v1/samples/server_groups/server-group-list-resp.json b/api-ref/source/v1/samples/server_groups/server-group-list-resp.json new file mode 100644 index 00000000..447dcd0c --- /dev/null +++ b/api-ref/source/v1/samples/server_groups/server-group-list-resp.json @@ -0,0 +1,48 @@ +{ + "server_groups": [ + { + "user_id": "9851baf53c75452dad7951bca7b3dbac", + "uuid": "73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "links": [ + { + "href": "http://10.229.40.107/v1/server_groups/73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "rel": "self" + }, + { + "href": "http://10.229.40.107/server_groups/73f9f8be-2e4e-4de8-b3f9-e1bf6618c1b1", + "rel": "bookmark" + } + ], + "created_at": "2017-08-02T09:18:24+00:00", + "updated_at": null, + "members": [], + "project_id": "b5f8b7e5429449a8a1366088abede8d1", + "policies": [ + "anti-affinity" + ], + "name": "test" + }, + { + "user_id": "9851baf53c75452dad7951bca7b3dbac", + "uuid": "fd2dab04-ab3e-4893-9ee4-837cc0ea2d9c", + "links": [ + { + "href": "http://10.229.40.107/v1/server_groups/fd2dab04-ab3e-4893-9ee4-837cc0ea2d9c", + "rel": "self" + }, + { + "href": "http://10.229.40.107/server_groups/fd2dab04-ab3e-4893-9ee4-837cc0ea2d9c", + "rel": "bookmark" + } + ], + "created_at": "2017-08-02T09:20:24+00:00", + "updated_at": null, + "members": [], + "project_id": "b5f8b7e5429449a8a1366088abede8d1", + "policies": [ + "affinity" + ], + "name": "test2" + } + ] +} diff --git a/api-ref/source/v1/samples/server_groups/server-group-post-req.json b/api-ref/source/v1/samples/server_groups/server-group-post-req.json new file mode 100644 index 00000000..82a37114 --- /dev/null +++ b/api-ref/source/v1/samples/server_groups/server-group-post-req.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "policies": [ + "anti-affinity" + ] +} + diff --git a/api-ref/source/v1/samples/server_groups/server-group-post-resp.json b/api-ref/source/v1/samples/server_groups/server-group-post-resp.json new file mode 100644 index 00000000..47e69ed5 --- /dev/null +++ b/api-ref/source/v1/samples/server_groups/server-group-post-resp.json @@ -0,0 +1,22 @@ +{ + "user_id": "9851baf53c75452dad7951bca7b3dbac", + "uuid": "159628a2-2f07-42cf-aebf-83bb5eb0ff3c", + "links": [ + { + "href": "http://10.229.40.107/v1/server_groups/159628a2-2f07-42cf-aebf-83bb5eb0ff3c", + "rel": "self" + }, + { + "href": "http://10.229.40.107/server_groups/159628a2-2f07-42cf-aebf-83bb5eb0ff3c", + "rel": "bookmark" + } + ], + "created_at": "2017-08-02T09:15:36+00:00", + "updated_at": null, + "members": [], + "project_id": "b5f8b7e5429449a8a1366088abede8d1", + "policies": [ + "anti-affinity" + ], + "name": "test" +} \ No newline at end of file diff --git a/api-ref/source/v1/server_groups.inc b/api-ref/source/v1/server_groups.inc new file mode 100644 index 00000000..10a5343d --- /dev/null +++ b/api-ref/source/v1/server_groups.inc @@ -0,0 +1,154 @@ +.. -*- rst -*- + +============= + ServerGroups +============= + +Lists, creates, shows and deletes server groups. + +List ServerGroups +================= + +.. rest_method:: GET /server_groups + +Lists server groups. + +Normal response codes: 200 + +Error response codes: unauthorized(401), forbidden(403) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - all_tenants: all_tenants + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - server_groups: server_groups + - name: server_group_name + - links: links + - uuid: server_group_uuid + - policies: server_group_policies + - members: server_group_members + - project_id: project_id_body + - user_id: user_id_body + - created_at: created_at + - updated_at: updated_at + +**Example List server groups: JSON response** + +.. literalinclude:: samples/server_groups/server-group-list-resp.json + :language: javascript + +Create ServerGroup +================== + +.. rest_method:: POST /server_groups + +Creates a server group. + +Normal response codes: 201 + +Error response codes: badRequest(400), unauthorized(401), forbidden(403), +conflict(409) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - name: server_group_name + - policies: server_group_policies + +**Example Create a ServerGroup: JSON request** + +.. literalinclude:: samples/server_groups/server-group-post-req.json + :language: javascript + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - name: server_group_name + - links: links + - uuid: server_group_uuid + - policies: server_group_policies + - members: server_group_members + - project_id: project_id_body + - user_id: user_id_body + - created_at: created_at + - updated_at: updated_at + +**Example Create ServerGroup: JSON response** + +.. literalinclude:: samples/server_groups/server-group-post-resp.json + :language: javascript + + +Show ServerGroup Details +======================== + +.. rest_method:: GET /server_groups/{server_group_uuid} + +Shows details for a server group. + +Normal response codes: 200 + +Error response codes: unauthorized(401), forbidden(403), itemNotFound(404) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - server_group_uuid: server_group_uuid_path + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - name: server_group_name + - links: links + - uuid: server_group_uuid + - policies: server_group_policies + - members: server_group_members + - project_id: project_id_body + - user_id: user_id_body + - created_at: created_at + - updated_at: updated_at + +**Example Show ServerGroup Details** + +.. literalinclude:: samples/server_groups/server-group-get-resp.json + :language: javascript + + +Delete a ServerGroup +==================== + +.. rest_method:: DELETE /server_groups/{server_group_uuid} + +Deletes a server group. + +Normal response codes: 204 + +Error response codes: unauthorized(401), forbidden(403), itemNotFound(404) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - server_group_uuid: server_group_uuid_path + +Response +-------- + +No body content is returned on a successful DELETE. diff --git a/mogan/api/controllers/v1/__init__.py b/mogan/api/controllers/v1/__init__.py index d9409f5e..397484de 100644 --- a/mogan/api/controllers/v1/__init__.py +++ b/mogan/api/controllers/v1/__init__.py @@ -30,6 +30,7 @@ from mogan.api.controllers.v1 import availability_zone from mogan.api.controllers.v1 import flavors from mogan.api.controllers.v1 import keypairs from mogan.api.controllers.v1 import nodes +from mogan.api.controllers.v1 import server_groups from mogan.api.controllers.v1 import servers from mogan.api import expose @@ -58,6 +59,9 @@ class V1(base.APIBase): nodes = [link.Link] """Links to the nodes resource""" + server_groups = [link.Link] + """Links to the server groups resource""" + @staticmethod def convert(): v1 = V1() @@ -108,6 +112,14 @@ class V1(base.APIBase): 'nodes', '', bookmark=True) ] + v1.server_groups = [link.Link.make_link('self', + pecan.request.public_url, + 'server_groups', ''), + link.Link.make_link('bookmark', + pecan.request.public_url, + 'server_groups', '', + bookmark=True) + ] return v1 @@ -120,6 +132,7 @@ class Controller(rest.RestController): keypairs = keypairs.KeyPairController() aggregates = aggregates.AggregateController() nodes = nodes.NodeController() + server_groups = server_groups.ServerGroupController() @expose.expose(V1) def get(self): diff --git a/mogan/api/controllers/v1/schemas/server_groups.py b/mogan/api/controllers/v1/schemas/server_groups.py new file mode 100644 index 00000000..89cbb8a3 --- /dev/null +++ b/mogan/api/controllers/v1/schemas/server_groups.py @@ -0,0 +1,34 @@ +# Copyright 2017 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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 mogan.api.validation import parameter_types + +create_server_group = { + 'type': 'object', + 'properties': { + 'name': parameter_types.name, + 'policies': { + 'type': 'array', + 'items': [{ + 'type': 'string', + 'enum': ['anti-affinity', 'affinity']}], + 'uniqueItems': True, + 'additionalItems': False, + } + }, + 'required': ['name', 'policies'], + 'additionalProperties': False, +} diff --git a/mogan/api/controllers/v1/server_groups.py b/mogan/api/controllers/v1/server_groups.py new file mode 100644 index 00000000..416752b7 --- /dev/null +++ b/mogan/api/controllers/v1/server_groups.py @@ -0,0 +1,155 @@ +# Copyright 2017 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +import pecan +from pecan import rest +from six.moves import http_client +import wsme +from wsme import types as wtypes + +from mogan.api.controllers import base +from mogan.api.controllers import link +from mogan.api.controllers.v1.schemas import server_groups as sg_schema +from mogan.api.controllers.v1 import types +from mogan.api import expose +from mogan.api import validation +from mogan.common import policy +from mogan import objects + + +class ServerGroup(base.APIBase): + """API representation of a server group. + + This class enforces type checking and value constraints, and converts + between the internal object model and the API representation of + a server group. + """ + uuid = types.uuid + """The UUID of the server group""" + + name = wtypes.text + """The name of the server group""" + + project_id = types.uuid + """The project UUID of the server group""" + + user_id = types.uuid + """The user UUID of the server group""" + + policies = [wtypes.text] + """The policies of the server group""" + + members = [types.uuid] + """The server members of the server group""" + + links = wsme.wsattr([link.Link], readonly=True) + """A list containing a self link""" + + def __init__(self, **kwargs): + self.fields = [] + for field in objects.ServerGroup.fields: + # Skip fields we do not expose. + if not hasattr(self, field): + continue + self.fields.append(field) + setattr(self, field, kwargs.get(field, wtypes.Unset)) + + @classmethod + def convert_with_links(cls, db_server_groups): + server_group = ServerGroup(**db_server_groups.as_dict()) + url = pecan.request.public_url + server_group.links = [link.Link.make_link('self', url, + 'server_groups', + server_group.uuid), + link.Link.make_link('bookmark', url, + 'server_groups', + server_group.uuid, + bookmark=True) + ] + + return server_group + + +class ServerGroupCollection(base.APIBase): + """API representation of a collection of server groups.""" + + server_groups = [ServerGroup] + """A list containing ServerGroup objects""" + + @staticmethod + def convert_with_links(server_groups, url=None, **kwargs): + collection = ServerGroupCollection() + collection.server_groups = [ServerGroup.convert_with_links( + server_group) for server_group in server_groups] + return collection + + +class ServerGroupController(rest.RestController): + """REST controller for server groups.""" + + @policy.authorize_wsgi("mogan:server_group", "get_all") + @expose.expose(ServerGroupCollection, types.boolean) + def get_all(self, all_tenants=False): + """Retrieve a list of server groups.""" + + if pecan.request.context.is_admin and all_tenants: + server_groups = objects.ServerGroupList.get_all( + pecan.request.context) + else: + project_id = pecan.request.context.project_id + server_groups = objects.ServerGroupList.get_by_project_id( + pecan.request.context, project_id) + return ServerGroupCollection.convert_with_links(server_groups) + + @policy.authorize_wsgi("mogan:server_group", "create") + @expose.expose(ServerGroup, body=types.jsontype, + status_code=http_client.CREATED) + def post(self, server_group): + """Create an new server group. + + :param server_group: a server group within the request body. + """ + validation.check_schema(server_group, sg_schema.create_server_group) + new_server_group = objects.ServerGroup(pecan.request.context, + **server_group) + new_server_group.project_id = pecan.request.context.project_id + new_server_group.user_id = pecan.request.context.user_id + new_server_group.create() + # Set the HTTP Location Header + pecan.response.location = link.build_url('server_groups', + new_server_group.uuid) + return ServerGroup.convert_with_links(new_server_group) + + @policy.authorize_wsgi("mogan:server_group", "get_one") + @expose.expose(ServerGroup, types.uuid) + def get_one(self, server_group_uuid): + """Retrieve information about the given server group. + + :param server_group_uuid: UUID of a server group. + """ + db_server_group = objects.ServerGroup.get_by_uuid( + pecan.request.context, server_group_uuid) + return ServerGroup.convert_with_links(db_server_group) + + @policy.authorize_wsgi("mogan:server_group", "delete") + @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT) + def delete(self, server_group_uuid): + """Delete a server group. + + :param server_group_uuid: UUID of a server group. + """ + db_server_group = objects.ServerGroup.get_by_uuid( + pecan.request.context, server_group_uuid) + db_server_group.destroy() diff --git a/mogan/common/policy.py b/mogan/common/policy.py index 7964bfa1..cc992786 100644 --- a/mogan/common/policy.py +++ b/mogan/common/policy.py @@ -171,6 +171,18 @@ server_policies = [ policy.RuleDefault('mogan:node:get_all', 'rule:admin_api', description='Get the nodes list'), + policy.RuleDefault('mogan:server_group:get_all', + 'rule:default', + description='Get the server group list'), + policy.RuleDefault('mogan:server_group:get_one', + 'rule:default', + description='Show a server group details'), + policy.RuleDefault('mogan:server_group:create', + 'rule:default', + description='Create a server group'), + policy.RuleDefault('mogan:server_group:delete', + 'rule:default', + description='Delete a server group'), ]