diff options
Diffstat (limited to 'cyborg_enhancement/mitaka_version/cyborg/cyborg/api')
18 files changed, 1552 insertions, 0 deletions
diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/__init__.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/__init__.py diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/app.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/app.py new file mode 100644 index 0000000..95862da --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/app.py @@ -0,0 +1,66 @@ +# 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 oslo_config import cfg + +from cyborg.api import config +from cyborg.api import hooks +from cyborg.api import middleware + + +def get_pecan_config(): + # Set up the pecan configuration + filename = config.__file__.replace('.pyc', '.py') + return pecan.configuration.conf_from_file(filename) + + +def setup_app(pecan_config=None, extra_hooks=None): + app_hooks = [hooks.ConfigHook(), + hooks.ConductorAPIHook(), + hooks.ContextHook(pecan_config.app.acl_public_routes), + hooks.PublicUrlHook()] + if extra_hooks: + app_hooks.extend(extra_hooks) + + if not pecan_config: + pecan_config = get_pecan_config() + + pecan.configuration.set_config(dict(pecan_config), overwrite=True) + + app = pecan.make_app( + pecan_config.app.root, + static_root=pecan_config.app.static_root, + debug=False, + force_canonical=getattr(pecan_config.app, 'force_canonical', True), + hooks=app_hooks, + wrap_app=middleware.ParsableErrorMiddleware + ) + + app = middleware.AuthTokenMiddleware( + app, dict(cfg.CONF), + public_api_routes=pecan_config.app.acl_public_routes) + + return app + + +class VersionSelectorApplication(object): + def __init__(self): + pc = get_pecan_config() + self.v1 = setup_app(pecan_config=pc) + + def __call__(self, environ, start_response): + return self.v1(environ, start_response) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/config.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/config.py new file mode 100644 index 0000000..32a0d27 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/config.py @@ -0,0 +1,40 @@ +# 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. + +# Server Specific Configurations +# See https://pecan.readthedocs.org/en/latest/configuration.html#server-configuration # noqa +server = { + 'port': '6666', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +# See https://pecan.readthedocs.org/en/latest/configuration.html#application-configuration # noqa +app = { + 'root': 'cyborg.api.controllers.root.RootController', + 'modules': ['cyborg.api'], + 'static_root': '%(confdir)s/public', + 'debug': False, + 'acl_public_routes': [ + '/', + '/v1' + ] +} + +# WSME Configurations +# See https://wsme.readthedocs.org/en/latest/integrate.html#configuration +wsme = { + 'debug': False +} 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 diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/expose.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/expose.py new file mode 100644 index 0000000..bcc92f4 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/expose.py @@ -0,0 +1,40 @@ +# 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 wsmeext.pecan as wsme_pecan +from pecan import expose as p_expose + +default_kargs = { + 'template': 'json', + 'content_type': 'application/json' +} + +def expose(*args, **kwargs): + """Ensure that only JSON, and not XML, is supported.""" + if 'rest_content_types' not in kwargs: + kwargs['rest_content_types'] = ('json',) + return wsme_pecan.wsexpose(*args, **kwargs) + +def content_expose(*args, **kwargs): + """Helper function so we don't have to specify json for everything.""" + kwargs.setdefault('template', default_kargs['template']) + kwargs.setdefault('content_type', default_kargs['content_type']) + return p_expose(*args, **kwargs) + +def when(index, *args, **kwargs): + """Helper function so we don't have to specify json for everything.""" + kwargs.setdefault('template', default_kargs['template']) + kwargs.setdefault('content_type', default_kargs['content_type']) + return index.when(*args, **kwargs) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/hooks.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/hooks.py new file mode 100644 index 0000000..6793982 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/hooks.py @@ -0,0 +1,112 @@ +# 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 oslo_config import cfg +from oslo_context import context +from pecan import hooks + +from cyborg.common import policy +from cyborg.conductor import rpcapi + + +class ConfigHook(hooks.PecanHook): + """Attach the config object to the request so controllers can get to it.""" + + def before(self, state): + state.request.cfg = cfg.CONF + + +class PublicUrlHook(hooks.PecanHook): + """Attach the right public_url to the request. + + Attach the right public_url to the request so resources can create + links even when the API service is behind a proxy or SSL terminator. + """ + + def before(self, state): + state.request.public_url = ( + cfg.CONF.api.public_endpoint or state.request.host_url) + + +class ConductorAPIHook(hooks.PecanHook): + """Attach the conductor_api object to the request.""" + + def __init__(self): + self.conductor_api = rpcapi.ConductorAPI() + + def before(self, state): + state.request.conductor_api = self.conductor_api + + +class ContextHook(hooks.PecanHook): + """Configures a request context and attaches it to the request. + + The following HTTP request headers are used: + + X-User-Id or X-User: + Used for context.user. + + X-Tenant-Id or X-Tenant: + Used for context.tenant. + + X-Auth-Token: + Used for context.auth_token. + + X-Roles: + Used for setting context.is_admin flag to either True or False. + The flag is set to True, if X-Roles contains either an administrator + or admin substring. Otherwise it is set to False. + + """ + + def __init__(self, public_api_routes): + self.public_api_routes = public_api_routes + super(ContextHook, self).__init__() + + def before(self, state): + headers = state.request.headers + ''' + creds = { + 'user_name': headers.get('X-User-Name'), + 'user': headers.get('X-User-Id'), + 'project_name': headers.get('X-Project-Name'), + 'tenant': headers.get('X-Project-Id'), + 'domain': headers.get('X-User-Domain-Id'), + 'domain_name': headers.get('X-User-Domain-Name'), + 'auth_token': headers.get('X-Auth-Token'), + 'roles': headers.get('X-Roles', '').split(','), + }''' + + creds = { + 'user': headers.get('X-User-Id'), + 'tenant': headers.get('X-Project-Id'), + 'domain': headers.get('X-User-Domain-Id',''), + 'auth_token': headers.get('X-Auth-Token'), + 'roles': headers.get('X-Roles', '').split(','), + } + + + is_admin = policy.authorize('is_admin', creds, creds) + state.request.context = context.RequestContext( + is_admin=is_admin, **creds) + + def after(self, state): + if state.request.context == {}: + # An incorrect url path will not create RequestContext + return + # RequestContext will generate a request_id if no one + # passing outside, so it always contain a request_id. + request_id = state.request.context.request_id + state.response.headers['Openstack-Request-Id'] = request_id diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/__init__.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/__init__.py new file mode 100644 index 0000000..95cc740 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/__init__.py @@ -0,0 +1,24 @@ +# 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 cyborg.api.middleware import auth_token +from cyborg.api.middleware import parsable_error + + +ParsableErrorMiddleware = parsable_error.ParsableErrorMiddleware +AuthTokenMiddleware = auth_token.AuthTokenMiddleware + +__all__ = ('ParsableErrorMiddleware', + 'AuthTokenMiddleware') diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/auth_token.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/auth_token.py new file mode 100644 index 0000000..95b5323 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/auth_token.py @@ -0,0 +1,64 @@ +# 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 re + +from keystonemiddleware import auth_token +from oslo_log import log + +from cyborg.common import exception +from cyborg.common.i18n import _ +from cyborg.common import utils + + +LOG = log.getLogger(__name__) + + +class AuthTokenMiddleware(auth_token.AuthProtocol): + """A wrapper on Keystone auth_token middleware. + + Does not perform verification of authentication tokens + for public routes in the API. + + """ + def __init__(self, app, conf, public_api_routes=None): + public_api_routes = public_api_routes or [] + self.app = app + route_pattern_tpl = '%s(\.json)?$' + + try: + self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl) + for route_tpl in public_api_routes] + except re.error as e: + msg = _('Cannot compile public API routes: %s') % e + + LOG.error(msg) + raise exception.ConfigInvalid(error_msg=msg) + + super(AuthTokenMiddleware, self).__init__(app, conf) + + def __call__(self, env, start_response): + path = utils.safe_rstrip(env.get('PATH_INFO'), '/') + + # The information whether the API call is being performed against the + # public API is required for some other components. Saving it to the + # WSGI environment is reasonable thereby. + env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path), + self.public_api_routes)) + + if env['is_public_api']: + return self.app(env, start_response) + + return super(AuthTokenMiddleware, self).__call__(env, start_response) diff --git a/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/parsable_error.py b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/parsable_error.py new file mode 100644 index 0000000..ba80c22 --- /dev/null +++ b/cyborg_enhancement/mitaka_version/cyborg/cyborg/api/middleware/parsable_error.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. + +""" +Middleware to replace the plain text message body of an error +response with one formatted so the client can parse it. + +Based on pecan.middleware.errordocument +""" + +import json + +import six + + +class ParsableErrorMiddleware(object): + """Replace error body with something the client can parse.""" + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + # Request for this state, modified by replace_start_response() + # and used when an error is being reported. + state = {} + + def replacement_start_response(status, headers, exc_info=None): + """Overrides the default response to make errors parsable.""" + try: + status_code = int(status.split(' ')[0]) + state['status_code'] = status_code + except (ValueError, TypeError): # pragma: nocover + raise Exception( + 'ParsableErrorMiddleware received an invalid ' + 'status %s' % status) + + if (state['status_code'] // 100) not in (2, 3): + # Remove some headers so we can replace them later + # when we have the full error message and can + # compute the length. + headers = [ + (h, v) for (h, v) in headers + if h not in ('Content-Length', 'Content-Type')] + + # Save the headers in case we need to modify them. + state['headers'] = headers + return start_response(status, headers, exc_info) + + app_iter = self.app(environ, replacement_start_response) + + if (state['status_code'] // 100) not in (2, 3): + if six.PY3: + app_iter = [i.decode('utf-8') for i in app_iter] + body = [json.dumps({'error_message': '\n'.join(app_iter)})] + if six.PY3: + body = [i.encode('utf-8') for i in body] + state['headers'].append(('Content-Type', 'application/json')) + state['headers'].append(('Content-Length', str(len(body[0])))) + else: + body = app_iter + return body |