# Copyright 2016 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 flavor_access from mogan.api.controllers.v1 import types from mogan.api import expose from mogan.api import validation from mogan.common import exception from mogan.common.i18n import _ from mogan import objects def _marshall_flavor_access(flavor): rval = [] for project_id in flavor.projects: rval.append(project_id) return {'flavor_access': rval} class Flavor(base.APIBase): """API representation of a flavor. This class enforces type checking and value constraints, and converts between the internal object model and the API representation of a flavor. """ uuid = types.uuid """The UUID of the flavor""" name = wtypes.text """The name of the flavor""" description = wtypes.text """The description of the flavor""" is_public = types.boolean """Indicates whether the flavor is public.""" extra_specs = {wtypes.text: types.jsontype} """The extra specs of the flavor""" links = wsme.wsattr([link.Link], readonly=True) """A list containing a self link""" def __init__(self, **kwargs): self.fields = [] for field in objects.InstanceType.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, rpc_flavor): flavor = Flavor(**rpc_flavor.as_dict()) url = pecan.request.public_url flavor.links = [link.Link.make_link('self', url, 'flavors', flavor.uuid), link.Link.make_link('bookmark', url, 'flavors', flavor.uuid, bookmark=True) ] return flavor class FlavorCollection(base.APIBase): """API representation of a collection of flavor.""" flavors = [Flavor] """A list containing Flavor objects""" @staticmethod def convert_with_links(flavors, url=None, **kwargs): collection = FlavorCollection() collection.flavors = [Flavor.convert_with_links(flavor) for flavor in flavors] return collection class FlavorExtraSpecsController(rest.RestController): """REST controller for flavor extra specs.""" @expose.expose(wtypes.text, types.uuid) def get_all(self, flavor_uuid): """Retrieve a list of extra specs of the queried flavor.""" flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) return dict(extra_specs=flavor.extra_specs) @expose.expose(types.jsontype, types.uuid, body=types.jsontype, status_code=http_client.ACCEPTED) def patch(self, flavor_uuid, extra_spec): """Create/update extra specs for the given flavor.""" flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) flavor.extra_specs = dict(flavor.extra_specs, **extra_spec) flavor.save() return dict(extra_specs=flavor.extra_specs) @expose.expose(None, types.uuid, wtypes.text, status_code=http_client.NO_CONTENT) def delete(self, flavor_uuid, spec_name): """Delete an extra specs for the given flavor.""" flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) del flavor.extra_specs[spec_name] flavor.save() class FlavorAccessController(rest.RestController): """REST controller for flavor access.""" @expose.expose(wtypes.text, types.uuid) def get_all(self, flavor_uuid): """Retrieve a list of extra specs of the queried flavor.""" flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) # public flavor to all projects if flavor.is_public: msg = _("Access list not available for public flavors.") raise wsme.exc.ClientSideError( msg, status_code=http_client.NOT_FOUND) # private flavor to listed projects only return _marshall_flavor_access(flavor) @expose.expose(None, types.uuid, body=types.jsontype, status_code=http_client.NO_CONTENT) def post(self, flavor_uuid, tenant): """Add flavor access for the given tenant.""" validation.check_schema(tenant, flavor_access.add_tenant_access) flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) if flavor.is_public: msg = _("Can not add access to a public flavor.") raise wsme.exc.ClientSideError( msg, status_code=http_client.CONFLICT) try: flavor.projects.append(tenant['tenant_id']) flavor.save() except exception.FlavorNotFound as e: raise wsme.exc.ClientSideError( e.message, status_code=http_client.NOT_FOUND) except exception.FlavorAccessExists as err: raise wsme.exc.ClientSideError( err.message, status_code=http_client.CONFLICT) @expose.expose(None, types.uuid, types.uuid, status_code=http_client.NO_CONTENT) def delete(self, flavor_uuid, tenant_id): """Remove flavor access for the given tenant.""" flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) try: # TODO(zhenguo): this should be synchronized. if tenant_id in flavor.projects: flavor.projects.remove(tenant_id) flavor.save() else: raise exception.FlavorAccessNotFound(flavor_id=flavor.uuid, project_id=tenant_id) except (exception.FlavorAccessNotFound, exception.FlavorNotFound) as e: raise wsme.exc.ClientSideError( e.message, status_code=http_client.NOT_FOUND) class FlavorsController(rest.RestController): """REST controller for Flavors.""" extraspecs = FlavorExtraSpecsController() access = FlavorAccessController() @expose.expose(FlavorCollection) def get_all(self): """Retrieve a list of flavor.""" flavors = objects.InstanceType.list(pecan.request.context) return FlavorCollection.convert_with_links(flavors) @expose.expose(Flavor, types.uuid) def get_one(self, flavor_uuid): """Retrieve information about the given flavor. :param flavor_uuid: UUID of a flavor. """ rpc_flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) return Flavor.convert_with_links(rpc_flavor) @expose.expose(Flavor, body=Flavor, status_code=http_client.CREATED) def post(self, flavor): """Create an new flavor. :param flavor: a flavor within the request body. """ new_flavor = objects.InstanceType(pecan.request.context, **flavor.as_dict()) new_flavor.create() # Set the HTTP Location Header pecan.response.location = link.build_url('flavors', new_flavor.uuid) return Flavor.convert_with_links(new_flavor) @expose.expose(Flavor, types.uuid, body=Flavor) def put(self, flavor_uuid, flavor): """Update a flavor. :param flavor_uuid: the uuid of the flavor to be updated. :param flavor: a flavor within the request body. """ try: flavor_in_db = objects.InstanceType.get( pecan.request.context, flavor_uuid) except exception.FlavorTypeNotFound: msg = (_("InstanceType %s could not be found") % flavor_uuid) raise wsme.exc.ClientSideError( msg, status_code=http_client.BAD_REQUEST) need_to_update = False for attr in ('name', 'description', 'is_public'): if getattr(flavor, attr) != wtypes.Unset: need_to_update = True setattr(flavor_in_db, attr, getattr(flavor, attr)) # don't need to call db_api if no update if need_to_update: flavor_in_db.save() # Set the HTTP Location Header pecan.response.location = link.build_url('flavor', flavor_in_db.uuid) return Flavor.convert_with_links(flavor_in_db) @expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT) def delete(self, flavor_uuid): """Delete a flavor. :param flavor_uuid: UUID of a flavor. """ rpc_flavor = objects.InstanceType.get(pecan.request.context, flavor_uuid) rpc_flavor.destroy()