diff options
Diffstat (limited to 'cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers')
10 files changed, 1134 insertions, 0 deletions
diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/__init__.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/__init__.py diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/base.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/base.py new file mode 100644 index 0000000..9131d3a --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/base.py @@ -0,0 +1,33 @@ +# 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 datetime + +import wsme +from wsme import types as wtypes + + +class APIBase(wtypes.Base): + created_at = wsme.wsattr(datetime.datetime, readonly=True) + """The time in UTC at which the object is created""" + + updated_at = wsme.wsattr(datetime.datetime, readonly=True) + """The time in UTC at which the object is updated""" + + def as_dict(self): + """Render this object as a dict of its fields.""" + return dict((k, getattr(self, k)) + for k in self.fields + if hasattr(self, k) and getattr(self, k) != wsme.Unset) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/link.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/link.py new file mode 100644 index 0000000..fe39c69 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/link.py @@ -0,0 +1,48 @@ +# 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 wsme import types as wtypes + +from cyborg.api.controllers import base + + +def build_url(resource, resource_args, bookmark=False, base_url=None): + if base_url is None: + base_url = pecan.request.public_url + + template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s' + template += '%(args)s' if resource_args.startswith('?') else '/%(args)s' + return template % {'url': base_url, 'res': resource, 'args': resource_args} + + +class Link(base.APIBase): + """A link representation.""" + + href = wtypes.text + """The url of a link.""" + + rel = wtypes.text + """The name of a link.""" + + type = wtypes.text + """Indicates the type of document/link.""" + + @staticmethod + def make_link(rel_name, url, resource, resource_args, + bookmark=False, type=wtypes.Unset): + href = build_url(resource, resource_args, + bookmark=bookmark, base_url=url) + return Link(href=href, rel=rel_name, type=type) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/root.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/root.py new file mode 100644 index 0000000..3361bfe --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/root.py @@ -0,0 +1,72 @@ +# 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 wsme import types as wtypes + +from cyborg.api.controllers import base +from cyborg.api.controllers import v1 +from cyborg.api import expose + + +VERSION1 = 'v1' + + +class Root(base.APIBase): + name = wtypes.text + """The name of the API""" + + description = wtypes.text + """Some information about this API""" + + @staticmethod + def convert(): + root = Root() + root.name = 'OpenStack Cyborg API' + root.description = ( + 'Cyborg (previously known as Nomad) is an ' + 'OpenStack project that aims to provide a general ' + 'purpose management framework for acceleration ' + 'resources (i.e. various types of accelerators ' + 'such as Crypto cards, GPU, FPGA, NVMe/NOF SSDs, ' + 'ODP, DPDK/SPDK and so on).') + return root + + +class RootController(rest.RestController): + _versions = [VERSION1] + """All supported API versions""" + + _default_version = VERSION1 + """The default API version""" + + v1 = v1.Controller() + + @expose.expose(Root) + def get(self): + return Root.convert() + + @pecan.expose() + def _route(self, args, request=None): + """Overrides the default routing behavior. + + It redirects the request to the default version of the cyborg API + if the version number is not specified in the url. + """ + + if args[0] and args[0] not in self._versions: + args = [self._default_version] + args + return super(RootController, self)._route(args) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/__init__.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/__init__.py new file mode 100644 index 0000000..661e7a0 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/__init__.py @@ -0,0 +1,73 @@ +# 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. + +"""Version 1 of the Cyborg API""" + +import pecan +from pecan import rest +from wsme import types as wtypes + +from cyborg.api.controllers import base +from cyborg.api.controllers import link +from cyborg.api.controllers.v1 import accelerators +from cyborg.api.controllers.v1 import ports +from cyborg.api.controllers.v1 import deployables +from cyborg.api import expose + + +class V1(base.APIBase): + """The representation of the version 1 of the API.""" + + id = wtypes.text + """The ID of the version""" + + accelerator = [link.Link] + """Links to the accelerator resource""" + + port = [link.Link] + """Links to the port resource""" + + @staticmethod + def convert(): + v1 = V1() + v1.id = 'v1' + v1.accelerator = [ + link.Link.make_link('self', pecan.request.public_url, + 'accelerator', ''), + link.Link.make_link('bookmark', pecan.request.public_url, + 'accelerator', '', bookmark=True) + ] + v1.port = [ + link.Link.make_link('self', pecan.request.public_url, + 'port', ''), + link.Link.make_link('bookmark', pecan.request.public_url, + 'port', '', bookmark=True) + ] + return v1 + + +class Controller(rest.RestController): + """Version 1 API controller root""" + + accelerators = accelerators.AcceleratorsController() + ports = ports.PortsController() + deployables = deployables.DeployablesController() + + @expose.expose(V1) + def get(self): + return V1.convert() + + +__all__ = ('Controller',) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/accelerators.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/accelerators.py new file mode 100644 index 0000000..e31d580 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/accelerators.py @@ -0,0 +1,233 @@ +# 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 cyborg.api.controllers import base +from cyborg.api.controllers import link +from cyborg.api.controllers.v1 import types +from cyborg.api import expose +from cyborg.common import policy +from cyborg import objects +from cyborg.api.controllers.v1 import utils as api_utils +from cyborg.common import exception + + +class Accelerator(base.APIBase): + """API representation of a accelerator. + + This class enforces type checking and value constraints, and converts + between the internal object model and the API representation of + a accelerator. + """ + + uuid = types.uuid + """The UUID of the accelerator""" + + name = wtypes.text + """The name of the accelerator""" + + description = wtypes.text + """The description of the accelerator""" + + project_id = types.uuid + """The project UUID of the accelerator""" + + user_id = types.uuid + """The user UUID of the accelerator""" + + device_type = wtypes.text + """The device type of the accelerator""" + + acc_type = wtypes.text + """The type of the accelerator""" + + acc_capability = wtypes.text + """The capability of the accelerator""" + + vendor_id = wtypes.text + """The vendor id of the accelerator""" + + product_id = wtypes.text + """The product id of the accelerator""" + + remotable = wtypes.IntegerType() + """Whether the accelerator is remotable""" + + links = wsme.wsattr([link.Link], readonly=True) + """A list containing a self link""" + + def __init__(self, **kwargs): + super(Accelerator, self).__init__(**kwargs) + self.fields = [] + for field in objects.Accelerator.fields: + self.fields.append(field) + setattr(self, field, kwargs.get(field, wtypes.Unset)) + + @classmethod + def convert_with_links(cls, obj_acc): + api_acc = cls(**obj_acc.as_dict()) + url = pecan.request.public_url + api_acc.links = [ + link.Link.make_link('self', url, 'accelerators', api_acc.uuid), + link.Link.make_link('bookmark', url, 'accelerators', api_acc.uuid, + bookmark=True) + ] + return api_acc + + +class AcceleratorCollection(base.APIBase): + """API representation of a collection of accelerators.""" + + accelerators = [Accelerator] + """A list containing accelerator objects""" + + @classmethod + def convert_with_links(cls, obj_accs): + collection = cls() + collection.accelerators = [Accelerator.convert_with_links(obj_acc) + for obj_acc in obj_accs] + return collection + + +class AcceleratorPatchType(types.JsonPatchType): + + _api_base = Accelerator + + @staticmethod + def internal_attrs(): + defaults = types.JsonPatchType.internal_attrs() + return defaults + ['/project_id', '/user_id', '/device_type', + '/acc_type', '/acc_capability', '/vendor_id', + '/product_id', '/remotable'] + + +class AcceleratorsControllerBase(rest.RestController): + + _resource = None + + def _get_resource(self, uuid): + self._resource = objects.Accelerator.get(pecan.request.context, uuid) + return self._resource + + +class AcceleratorsController(AcceleratorsControllerBase): + """REST controller for Accelerators.""" + + @policy.authorize_wsgi("cyborg:accelerator", "create", False) + @expose.expose(Accelerator, body=types.jsontype, + status_code=http_client.CREATED) + def post(self, acc): + """Create a new accelerator. + + :param acc: an accelerator within the request body. + """ + context = pecan.request.context + obj_acc = objects.Accelerator(context, **acc) + new_acc = pecan.request.conductor_api.accelerator_create( + context, obj_acc ) + # Set the HTTP Location Header + pecan.response.location = link.build_url('accelerators', new_acc.uuid) + return Accelerator.convert_with_links(new_acc) + + @policy.authorize_wsgi("cyborg:accelerator", "get") + @expose.expose(Accelerator, types.uuid) + def get_one(self, uuid): + """Retrieve information about the given accelerator. + + :param uuid: UUID of an accelerator. + """ + obj_acc = self._resource or self._get_resource(uuid) + return Accelerator.convert_with_links(obj_acc) + + @expose.expose(AcceleratorCollection, int, types.uuid, wtypes.text, + wtypes.text, types.boolean) + def get_all(self, limit=None, marker=None, sort_key='id', sort_dir='asc', + all_tenants=None): + """Retrieve a list of accelerators. + + :param limit: Optional, to determinate the maximum number of + accelerators to return. + :param marker: Optional, to display a list of accelerators after this + marker. + :param sort_key: Optional, to sort the returned accelerators list by + this specified key value. + :param sort_dir: Optional, to return a list of accelerators with this + sort direction. + :param all_tenants: Optional, allows administrators to see the + accelerators owned by all tenants, otherwise only + the accelerators associated with the calling + tenant are included in the response. + """ + context = pecan.request.context + project_only = True + if context.is_admin and all_tenants: + project_only = False + + marker_obj = None + if marker: + marker_obj = objects.Accelerator.get(context, marker) + + obj_accs = objects.Accelerator.list(context, limit, marker_obj, + sort_key, sort_dir, project_only) + return AcceleratorCollection.convert_with_links(obj_accs) + + @policy.authorize_wsgi("cyborg:accelerator", "update") + @expose.expose(Accelerator, types.uuid, body=[AcceleratorPatchType]) + def patch(self, uuid, patch): + """Update an accelerator. + + :param uuid: UUID of an accelerator. + :param patch: a json PATCH document to apply to this accelerator. + """ + obj_acc = self._resource or self._get_resource(uuid) + try: + api_acc = Accelerator( + **api_utils.apply_jsonpatch(obj_acc.as_dict(), patch)) + except api_utils.JSONPATCH_EXCEPTIONS as e: + raise exception.PatchError(patch=patch, reason=e) + + # Update only the fields that have changed + for field in objects.Accelerator.fields: + try: + patch_val = getattr(api_acc, field) + except AttributeError: + # Ignore fields that aren't exposed in the API + continue + if patch_val == wtypes.Unset: + patch_val = None + if obj_acc[field] != patch_val: + obj_acc[field] = patch_val + + context = pecan.request.context + new_acc = pecan.request.conductor_api.accelerator_update(context, + obj_acc) + return Accelerator.convert_with_links(new_acc) + + @policy.authorize_wsgi("cyborg:accelerator", "delete") + @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT) + def delete(self, uuid): + """Delete an accelerator. + + :param uuid: UUID of an accelerator. + """ + obj_acc = self._resource or self._get_resource(uuid) + context = pecan.request.context + pecan.request.conductor_api.accelerator_delete(context, obj_acc) + diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/deployables.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/deployables.py new file mode 100644 index 0000000..8a4a12f --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/deployables.py @@ -0,0 +1,210 @@ +# Copyright 2018 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 cyborg.api.controllers import base
+from cyborg.api.controllers import link
+from cyborg.api.controllers.v1 import types
+from cyborg.api.controllers.v1 import utils as api_utils
+from cyborg.api import expose
+from cyborg.common import exception
+from cyborg.common import policy
+from cyborg import objects
+
+
+class Deployable(base.APIBase):
+ """API representation of a deployable.
+ This class enforces type checking and value constraints, and converts
+ between the internal object model and the API representation of
+ a deployable.
+ """
+
+ uuid = types.uuid
+ """The UUID of the deployable"""
+
+ name = wtypes.text
+ """The name of the deployable"""
+
+ parent_uuid = types.uuid
+ """The parent UUID of the deployable"""
+
+ root_uuid = types.uuid
+ """The root UUID of the deployable"""
+
+ pcie_address = wtypes.text
+ """The pcie address of the deployable"""
+
+ host = wtypes.text
+ """The host on which the deployable is located"""
+
+ board = wtypes.text
+ """The board of the deployable"""
+
+ vendor = wtypes.text
+ """The vendor of the deployable"""
+
+ version = wtypes.text
+ """The version of the deployable"""
+
+ type = wtypes.text
+ """The type of the deployable"""
+
+ assignable = types.boolean
+ """Whether the deployable is assignable"""
+
+ instance_uuid = types.uuid
+ """The UUID of the instance which deployable is assigned to"""
+
+ availability = wtypes.text
+ """The availability of the deployable"""
+
+ links = wsme.wsattr([link.Link], readonly=True)
+ """A list containing a self link"""
+
+ def __init__(self, **kwargs):
+ super(Deployable, self).__init__(**kwargs)
+ self.fields = []
+ for field in objects.Deployable.fields:
+ self.fields.append(field)
+ setattr(self, field, kwargs.get(field, wtypes.Unset))
+
+ @classmethod
+ def convert_with_links(cls, obj_dep):
+ api_dep = cls(**obj_dep.as_dict())
+ url = pecan.request.public_url
+ api_dep.links = [
+ link.Link.make_link('self', url, 'deployables', api_dep.uuid),
+ link.Link.make_link('bookmark', url, 'deployables', api_dep.uuid,
+ bookmark=True)
+ ]
+ return api_dep
+
+
+class DeployableCollection(base.APIBase):
+ """API representation of a collection of deployables."""
+
+ deployables = [Deployable]
+ """A list containing deployable objects"""
+
+ @classmethod
+ def convert_with_links(cls, obj_deps):
+ collection = cls()
+ collection.deployables = [Deployable.convert_with_links(obj_dep)
+ for obj_dep in obj_deps]
+ return collection
+
+
+class DeployablePatchType(types.JsonPatchType):
+
+ _api_base = Deployable
+
+ @staticmethod
+ def internal_attrs():
+ defaults = types.JsonPatchType.internal_attrs()
+ return defaults + ['/pcie_address', '/host', '/type']
+
+
+class DeployablesController(rest.RestController):
+ """REST controller for Deployables."""
+
+ @policy.authorize_wsgi("cyborg:deployable", "create", False)
+ @expose.expose(Deployable, body=types.jsontype,
+ status_code=http_client.CREATED)
+ def post(self, dep):
+ """Create a new deployable.
+ :param dep: a deployable within the request body.
+ """
+ context = pecan.request.context
+ obj_dep = objects.Deployable(context, **dep)
+ new_dep = pecan.request.conductor_api.deployable_create(context,
+ obj_dep)
+ # Set the HTTP Location Header
+ pecan.response.location = link.build_url('deployables', new_dep.uuid)
+ return Deployable.convert_with_links(new_dep)
+
+ @policy.authorize_wsgi("cyborg:deployable", "get_one")
+ @expose.expose(Deployable, types.uuid)
+ def get_one(self, uuid):
+ """Retrieve information about the given deployable.
+ :param uuid: UUID of a deployable.
+ """
+
+ obj_dep = objects.Deployable.get(pecan.request.context, uuid)
+ return Deployable.convert_with_links(obj_dep)
+
+ @policy.authorize_wsgi("cyborg:deployable", "get_all")
+ @expose.expose(DeployableCollection, types.uuid, wtypes.text,
+ wtypes.text, types.boolean)
+ def get_all(self, root_uuid=None, host=None, type=None, assignable=None):
+ """Retrieve a list of deployables."""
+ filters = {}
+ if root_uuid:
+ filters["root_uuid"] = root_uuid
+ if host:
+ filters["host"] = host
+ if type:
+ filters["type"] = type
+ if assignable:
+ filters["assignable"] = assignable
+ obj_deps = objects.Deployable.get_by_filter(pecan.request.context,
+ filters=filters)
+ return DeployableCollection.convert_with_links(obj_deps)
+
+ @policy.authorize_wsgi("cyborg:deployable", "update")
+ @expose.expose(Deployable, types.uuid, body=[DeployablePatchType])
+ def patch(self, uuid, patch):
+ """Update a deployable.
+ :param uuid: UUID of a deployable.
+ :param patch: a json PATCH document to apply to this deployable.
+ """
+ context = pecan.request.context
+ obj_dep = objects.Deployable.get(context, uuid)
+
+ try:
+ api_dep = Deployable(
+ **api_utils.apply_jsonpatch(obj_dep.as_dict(), patch))
+ except api_utils.JSONPATCH_EXCEPTIONS as e:
+ raise exception.PatchError(patch=patch, reason=e)
+
+ # Update only the fields that have changed
+ for field in objects.Deployable.fields:
+ try:
+ patch_val = getattr(api_dep, field)
+ except AttributeError:
+ # Ignore fields that aren't exposed in the API
+ continue
+ if patch_val == wtypes.Unset:
+ patch_val = None
+ if obj_dep[field] != patch_val:
+ obj_dep[field] = patch_val
+
+ new_dep = pecan.request.conductor_api.deployable_update(context,
+ obj_dep)
+ return Deployable.convert_with_links(new_dep)
+
+ @policy.authorize_wsgi("cyborg:deployable", "delete")
+ @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT)
+ def delete(self, uuid):
+ """Delete a deployable.
+ :param uuid: UUID of a deployable.
+ """
+ context = pecan.request.context
+ obj_dep = objects.Deployable.get(context, uuid)
+ pecan.request.conductor_api.deployable_delete(context, obj_dep)
\ No newline at end of file diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/ports.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/ports.py new file mode 100644 index 0000000..7d6c1dd --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/ports.py @@ -0,0 +1,269 @@ +# Copyright 2018 Lenovo Research 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 cyborg.api.controllers import base +from cyborg.api.controllers import link +from cyborg.api.controllers.v1 import types +from cyborg.api import expose +from pecan import expose as pexpose +from cyborg.common import policy +from cyborg import objects +from cyborg.api.controllers.v1 import utils as api_utils +from cyborg.common import exception + +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + +class Port(base.APIBase): + """API representation of a port. + + This class enforces type checking and value constraints, and converts + between the internal object model and the API representation of + a port. + """ + + uuid = types.uuid + computer_id = types.uuid + phy_port_name = wtypes.text + pci_slot = wtypes.text + product_id = wtypes.text + vendor_id = wtypes.text + is_used = wtypes.IntegerType() + accelerator_id = types.uuid + bind_instance_id = types.uuid + bind_port_id = types.uuid + device_type = wtypes.text + + links = wsme.wsattr([link.Link], readonly=True) + """A list containing a self link""" + + def __init__(self, **kwargs): + self.fields = [] + for field in objects.Port.fields: + self.fields.append(field) + setattr(self, field, kwargs.get(field, wtypes.Unset)) + + @classmethod + def convert_with_links(cls, rpc_acc): + port = Port(**rpc_acc.as_dict()) + url = pecan.request.public_url + port.links = [ + link.Link.make_link('self', url, 'ports', + port.uuid), + link.Link.make_link('bookmark', url, 'ports', + port.uuid, bookmark=True) + ] + + return port + + + +class PortCollection(base.APIBase): + """API representation of a collection of ports.""" + + ports = [Port] + """A list containing port objects""" + + @classmethod + def convert_with_links(cls, rpc_ports): + collection = cls() + collection.ports = [Port.convert_with_links(obj_port) + for obj_port in rpc_ports] + return collection + + +class PortPatchType(types.JsonPatchType): + + _api_base = Port + + @staticmethod + def internal_attrs(): + defaults = types.JsonPatchType.internal_attrs() + return defaults + ['/computer_id', '/phy_port_name', '/pci_slot', + '/vendor_id', '/product_id'] + + +class PortsControllerBase(rest.RestController): + _resource = None + def _get_resource(self, uuid): + self._resource = objects.Port.get(pecan.request.context, uuid) + return self._resource + + +class BindPortController(PortsControllerBase): + # url path: /v1/ports/bind/{uuid} + + @expose.expose(Port, body=types.jsontype) + def put(self, uuid, patch): + """bind a existing port to a logical neutron port. + : param uuid: UUID of a port. + : param patch: a json type to apply to this port. + """ + context = pecan.request.context + obj_port = self._resource or self._get_resource(uuid) + # object with user modified properties. + mod_port = objects.Port(context, **patch) + + # update fields used in bind. + obj_port["accelerator_id"] = mod_port["accelerator_id"] + obj_port["bind_instance_id"] = mod_port["bind_instance_id"] + obj_port["bind_port_id"] = mod_port["bind_port_id"] + obj_port["is_used"] = mod_port["is_used"] + obj_port["device_type"] = mod_port["device_type"] + + LOG.debug(obj_port) + new_port = pecan.request.conductor_api.port_update(context, obj_port) + return Port.convert_with_links(new_port) + +class UnBindPortController(PortsControllerBase): + # url path: /v1/ports/bind/{uuid} + + @expose.expose(Port, body=types.jsontype) + def put(self, uuid): + """unbind a existing port, set some areas to null in DB. + : param uuid: UUID of a port. + : param patch: a json type to apply to this port. + """ + context = pecan.request.context + obj_port = self._resource or self._get_resource(uuid) + + # update fields used in unbind. + obj_port["accelerator_id"] = None + obj_port["bind_instance_id"] = None + obj_port["bind_port_id"] = None + obj_port["is_used"] = 0 + obj_port["device_type"] = None + + new_port = pecan.request.conductor_api.port_update(context, obj_port) + return Port.convert_with_links(new_port) + + +class PortsController(PortsControllerBase): + """REST controller for Ports. + url path: /v2.0/ports/ + """ + bind = BindPortController() + unbind = UnBindPortController() + + @policy.authorize_wsgi("cyborg:port", "create", False) + @expose.expose(Port, body=types.jsontype, + status_code=http_client.CREATED) + def post(self, port): + """Create a new port. + + :param port: an port within the request body. + """ + context = pecan.request.context + rpc_port = objects.Port(context, **port) + new_port = pecan.request.conductor_api.port_create( + context, rpc_port) + # Set the HTTP Location Header + pecan.response.location = link.build_url('ports', + new_port.uuid) + return Port.convert_with_links(new_port) + + #@policy.authorize_wsgi("cyborg:port", "get") + @expose.expose(Port, types.uuid) + def get_one(self, uuid): + """Retrieve information about the given uuid port. + : param uuid: UUID of a port. + """ + rpc_port = self._get_resource(uuid) + if rpc_port == None: + return pecan.abort(404, detail='The uuid Not Found.') + else: + return Port.convert_with_links(rpc_port) + + @expose.expose(PortCollection, int, types.uuid, wtypes.text, + wtypes.text, types.boolean) + def get_all(self, limit = None, marker = None, sort_key='id', + sort_dir='asc'): + """Retrieve a list of ports. + : param limit: Optional, to determine the maximum number of + ports to return. + : param marker: Optional, to display a list of ports after + this marker. + : param sort_dir: Optional, to return a list of ports with this + sort direction. + : param all_tenants: Optional, allows administrators to see the + ports owned by all tenants, otherwise only the ports + associated with the calling tenant are included in the response.""" + + context = pecan.request.context + marker_obj = None; + if marker: + marker_obj = objects.Port.get(context, marker) + + rpc_ports = objects.Port.list( + context, limit, marker_obj, sort_key, sort_dir) + + return PortCollection.convert_with_links(rpc_ports) + + #@policy.authorize_wsgi("cyborg:port", "update") + @expose.expose(Port, types.uuid, body=[PortPatchType]) + def put(self, uuid, patch): + """Update an port's property. + : param uuid: UUID of a port. + : param patch: a json PATCH document to apply to this port. + """ + obj_port = self._resource or self._get_resource(uuid) + try: + api_port = Port(**api_utils.apply_jsonpatch(obj_port.as_dict(), patch)) + except api_utils.JSONPATCH_EXCEPTIONS as e: + raise exception.PatchError(patch=patch, reason=e) + + #update only the fields that have changed. + for field in objects.Port.fields: + try: + patch_val = getattr(api_port, field) + except AttributeError: + # Ignore fields that aren't exposed in the API + continue + + if patch_val == wtypes.Unset: + patch_val = None + if obj_port[field] != patch_val: + obj_port[field] = patch_val + + context = pecan.request.context + new_port = pecan.request.conductor_api.port_update(context, obj_port) + return Port.convert_with_links(new_port) + + + #@policy.authorize_wsgi("cyborg:port", "delete") + @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT) + def delete(self, uuid): + """Delete a port. + :param uuid: UUID of the port.""" + + rpc_port = self._resource or self._get_resource(uuid) + if rpc_port == None: + status_code = http_client.NOT_FOUND + context = pecan.request.context + pecan.request.conductor_api.port_delete(context, rpc_port) + + + + + + + diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/types.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/types.py new file mode 100644 index 0000000..61ce387 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/types.py @@ -0,0 +1,161 @@ +# 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 json + +from oslo_utils import uuidutils +from wsme import types as wtypes +import wsme + +import inspect +from oslo_utils import strutils +from cyborg.common.i18n import _ +from cyborg.common import exception + + +class UUIDType(wtypes.UserType): + """A simple UUID type.""" + + basetype = wtypes.text + name = 'uuid' + + @staticmethod + def validate(value): + if not uuidutils.is_uuid_like(value): + raise exception.InvalidUUID(uuid=value) + return value + + @staticmethod + def frombasetype(value): + if value is None: + return None + return UUIDType.validate(value) + + +class JsonType(wtypes.UserType): + """A simple JSON type.""" + + basetype = wtypes.text + name = 'json' + + @staticmethod + def validate(value): + try: + json.dumps(value) + except TypeError: + raise exception.InvalidJsonType(value=value) + else: + return value + + @staticmethod + def frombasetype(value): + return JsonType.validate(value) + + +class BooleanType(wtypes.UserType): + """A simple boolean type.""" + + basetype = wtypes.text + name = 'boolean' + + @staticmethod + def validate(value): + try: + return strutils.bool_from_string(value, strict=True) + except ValueError as e: + # raise Invalid to return 400 (BadRequest) in the API + raise exception.Invalid(e) + + @staticmethod + def frombasetype(value): + if value is None: + return None + return BooleanType.validate(value) + + +uuid = UUIDType() +jsontype = JsonType() +boolean = BooleanType() + + +class JsonPatchType(wtypes.Base): + """A complex type that represents a single json-patch operation.""" + + path = wtypes.wsattr(wtypes.StringType(pattern='^(/[\w-]+)+$'), + mandatory=True) + op = wtypes.wsattr(wtypes.Enum(str, 'add', 'replace', 'remove'), + mandatory=True) + value = wtypes.wsattr(jsontype, default=wtypes.Unset) + + # The class of the objects being patched. Override this in subclasses. + # Should probably be a subclass of cyborg.api.controllers.base.APIBase. + _api_base = None + + # Attributes that are not required for construction, but which may not be + # removed if set. Override in subclasses if needed. + _extra_non_removable_attrs = set() + + # Set of non-removable attributes, calculated lazily. + _non_removable_attrs = None + + @staticmethod + def internal_attrs(): + """Returns a list of internal attributes. + + Internal attributes can't be added, replaced or removed. This + method may be overwritten by derived class. + + """ + return ['/created_at', '/id', '/links', '/updated_at', '/uuid'] + + @classmethod + def non_removable_attrs(cls): + """Returns a set of names of attributes that may not be removed. + + Attributes whose 'mandatory' property is True are automatically added + to this set. To add additional attributes to the set, override the + field _extra_non_removable_attrs in subclasses, with a set of the form + {'/foo', '/bar'}. + """ + if cls._non_removable_attrs is None: + cls._non_removable_attrs = cls._extra_non_removable_attrs.copy() + if cls._api_base: + fields = inspect.getmembers(cls._api_base, + lambda a: not inspect.isroutine(a)) + for name, field in fields: + if getattr(field, 'mandatory', False): + cls._non_removable_attrs.add('/%s' % name) + return cls._non_removable_attrs + + @staticmethod + def validate(patch): + _path = '/' + patch.path.split('/')[1] + if _path in patch.internal_attrs(): + msg = _("'%s' is an internal attribute and can not be updated") + raise wsme.exc.ClientSideError(msg % patch.path) + + if patch.path in patch.non_removable_attrs() and patch.op == 'remove': + msg = _("'%s' is a mandatory attribute and can not be removed") + raise wsme.exc.ClientSideError(msg % patch.path) + + if patch.op != 'remove': + if patch.value is wsme.Unset: + msg = _("'add' and 'replace' operations need a value") + raise wsme.exc.ClientSideError(msg) + + ret = {'path': patch.path, 'op': patch.op} + if patch.value is not wsme.Unset: + ret['value'] = patch.value + return ret diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/utils.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/utils.py new file mode 100644 index 0000000..6c88d3e --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/controllers/v1/utils.py @@ -0,0 +1,35 @@ +# 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 jsonpatch
+import wsme
+
+
+from cyborg.common.i18n import _
+
+
+JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
+ jsonpatch.JsonPointerException,
+ KeyError)
+
+
+def apply_jsonpatch(doc, patch):
+ for p in patch:
+ if p['op'] == 'add' and p['path'].count('/') == 1:
+ if p['path'].lstrip('/') not in doc:
+ msg = _('Adding a new attribute (%s) to the root of '
+ ' the resource is not allowed')
+ raise wsme.exc.ClientSideError(msg % p['path'])
+ return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
\ No newline at end of file |