diff --git a/tricircle/cinder_apigw/controllers/root.py b/tricircle/cinder_apigw/controllers/root.py index 20d39ab..f848558 100644 --- a/tricircle/cinder_apigw/controllers/root.py +++ b/tricircle/cinder_apigw/controllers/root.py @@ -18,8 +18,10 @@ import pecan import oslo_log.log as logging from tricircle.cinder_apigw.controllers import volume +from tricircle.cinder_apigw.controllers import volume_metadata from tricircle.cinder_apigw.controllers import volume_type + LOG = logging.getLogger(__name__) @@ -69,6 +71,10 @@ class V2Controller(object): 'types': volume_type.VolumeTypeController } + self.volumes_sub_controller = { + 'metadata': volume_metadata.VolumeMetaDataController, + } + @pecan.expose() def _lookup(self, tenant_id, *remainder): if not remainder: @@ -78,6 +84,14 @@ class V2Controller(object): if resource not in self.resource_controller: pecan.abort(404) return + if resource == 'volumes' and len(remainder) >= 3: + volume_id = remainder[1] + sub_resource = remainder[2] + if sub_resource not in self.volumes_sub_controller: + pecan.abort(404) + return + return self.volumes_sub_controller[sub_resource]( + tenant_id, volume_id), remainder[3:] return self.resource_controller[resource](tenant_id), remainder[1:] @pecan.expose(generic=True, template='json') diff --git a/tricircle/cinder_apigw/controllers/volume.py b/tricircle/cinder_apigw/controllers/volume.py index 979fc97..9df4acb 100644 --- a/tricircle/cinder_apigw/controllers/volume.py +++ b/tricircle/cinder_apigw/controllers/volume.py @@ -85,15 +85,15 @@ class VolumeController(rest.RestController): return utils.format_cinder_error( 500, _('Bottom Pod endpoint incorrect')) - b_headers = self._convert_header(t_release, - b_release, - request.headers) + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) t_vol = kw['volume'] # add or remove key-value in the request for diff. version - b_vol_req = self._convert_object(t_release, b_release, t_vol, - res_type=cons.RT_VOLUME) + b_vol_req = hclient.convert_object(t_release, b_release, t_vol, + res_type=cons.RT_VOLUME) # convert az to the configured one # remove the AZ parameter to bottom request for default one @@ -142,9 +142,9 @@ class VolumeController(rest.RestController): return utils.format_cinder_error( 500, _('Failed to create volume resource routing')) - ret_vol = self._convert_object(b_release, t_release, - b_vol_ret, - res_type=cons.RT_VOLUME) + ret_vol = hclient.convert_object(b_release, t_release, + b_vol_ret, + res_type=cons.RT_VOLUME) ret_vol['availability_zone'] = pod['az_name'] @@ -163,11 +163,12 @@ class VolumeController(rest.RestController): t_release = cons.R_MITAKA b_release = cons.R_MITAKA - b_headers = self._convert_header(t_release, - b_release, - request.headers) + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) - s_ctx = self._get_res_routing_ref(context, _id, request.url) + s_ctx = hclient.get_res_routing_ref(context, _id, request.url, + cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) @@ -188,11 +189,11 @@ class VolumeController(rest.RestController): if b_status == 200: if b_ret_body.get('volume') is not None: b_vol_ret = b_ret_body['volume'] - ret_vol = self._convert_object(b_release, t_release, - b_vol_ret, - res_type=cons.RT_VOLUME) + ret_vol = hclient.convert_object(b_release, t_release, + b_vol_ret, + res_type=cons.RT_VOLUME) - pod = self._get_pod_by_top_id(context, _id) + pod = utils.get_pod_by_top_id(context, _id) if pod: ret_vol['availability_zone'] = pod['az_name'] @@ -288,7 +289,8 @@ class VolumeController(rest.RestController): t_release = cons.R_MITAKA b_release = cons.R_MITAKA - s_ctx = self._get_res_routing_ref(context, _id, request.url) + s_ctx = hclient.get_res_routing_ref(context, _id, request.url, + cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) @@ -297,15 +299,15 @@ class VolumeController(rest.RestController): return utils.format_cinder_error( 404, _('Bottom Pod endpoint incorrect')) - b_headers = self._convert_header(t_release, - b_release, - request.headers) + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) t_vol = kw['volume'] # add or remove key-value in the request for diff. version - b_vol_req = self._convert_object(t_release, b_release, t_vol, - res_type=cons.RT_VOLUME) + b_vol_req = hclient.convert_object(t_release, b_release, t_vol, + res_type=cons.RT_VOLUME) b_body = jsonutils.dumps({'volume': b_vol_req}) @@ -321,11 +323,11 @@ class VolumeController(rest.RestController): if b_status == 200: if b_ret_body.get('volume') is not None: b_vol_ret = b_ret_body['volume'] - ret_vol = self._convert_object(b_release, t_release, - b_vol_ret, - res_type=cons.RT_VOLUME) + ret_vol = hclient.convert_object(b_release, t_release, + b_vol_ret, + res_type=cons.RT_VOLUME) - pod = self._get_pod_by_top_id(context, _id) + pod = utils.get_pod_by_top_id(context, _id) if pod: ret_vol['availability_zone'] = pod['az_name'] @@ -351,7 +353,8 @@ class VolumeController(rest.RestController): t_release = cons.R_MITAKA b_release = cons.R_MITAKA - s_ctx = self._get_res_routing_ref(context, _id, request.url) + s_ctx = hclient.get_res_routing_ref(context, _id, request.url, + cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) @@ -360,9 +363,9 @@ class VolumeController(rest.RestController): return utils.format_cinder_error( 404, _('Bottom Pod endpoint incorrect')) - b_headers = self._convert_header(t_release, - b_release, - request.headers) + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) resp = hclient.forward_req(context, 'DELETE', b_headers, @@ -373,49 +376,5 @@ class VolumeController(rest.RestController): # don't remove the resource routing for delete is async. operation # remove the routing when query is executed but not find - # No content in the resp actually return response - - # move to common function if other modules need - def _get_res_routing_ref(self, context, _id, t_url): - - pod = self._get_pod_by_top_id(context, _id) - - if not pod: - return None - - pod_name = pod['pod_name'] - - s_ctx = hclient.get_pod_service_ctx( - context, - t_url, - pod_name, - s_type=cons.ST_CINDER) - - if s_ctx['b_url'] == '': - LOG.error(_LE("bottom pod endpoint incorrect %s") % - pod_name) - - return s_ctx - - # move to common function if other modules need - def _get_pod_by_top_id(self, context, _id): - - mappings = db_api.get_bottom_mappings_by_top_id( - context, _id, - cons.RT_VOLUME) - - if not mappings or len(mappings) != 1: - return None - - return mappings[0][0] - - def _convert_header(self, from_release, to_release, header): - - return header - - def _convert_object(self, from_release, to_release, res_object, - res_type=cons.RT_VOLUME): - - return res_object diff --git a/tricircle/cinder_apigw/controllers/volume_metadata.py b/tricircle/cinder_apigw/controllers/volume_metadata.py new file mode 100644 index 0000000..18485d5 --- /dev/null +++ b/tricircle/cinder_apigw/controllers/volume_metadata.py @@ -0,0 +1,287 @@ +# Copyright 2016 OpenStack Foundation. +# 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 pecan import expose +from pecan import request +from pecan import response +from pecan import rest + +from oslo_log import log as logging +from oslo_serialization import jsonutils + +from tricircle.common import constants as cons +import tricircle.common.context as t_context +from tricircle.common import httpclient as hclient +from tricircle.common.i18n import _ +from tricircle.common.i18n import _LE +from tricircle.common import utils +import tricircle.db.api as db_api + +LOG = logging.getLogger(__name__) + + +class VolumeMetaDataController(rest.RestController): + + def __init__(self, tenant_id, volume_id): + self.tenant_id = tenant_id + self.volume_id = volume_id + + @expose(generic=True, template='json') + def post(self, **kw): + """Create volume metadata associated with a volume. + + :param kw: dictionary of values to be created + :returns: created volume metadata + """ + context = t_context.extract_context_from_environ() + + if 'metadata' not in kw: + return utils.format_cinder_error( + 400, _("Missing required element 'metadata' in " + "request body.")) + + try: + pod = utils.get_pod_by_top_id(context, self.volume_id) + if pod is None: + return utils.format_cinder_error( + 404, _('Volume %(volume_id)s could not be found.') % { + 'volume_id': self.volume_id + }) + + t_pod = db_api.get_top_pod(context) + if not t_pod: + LOG.error(_LE("Top Pod not configured")) + return utils.format_cinder_error( + 500, _('Top Pod not configured')) + except Exception as e: + LOG.exception(_LE('Fail to create metadata for a volume:' + '%(volume_id)s' + '%(exception)s'), + {'volume_id': self.volume_id, + 'exception': e}) + return utils.format_cinder_error(500, _('Fail to create metadata')) + + t_release = cons.R_MITAKA + b_release = cons.R_MITAKA + + s_ctx = hclient.get_pod_service_ctx( + context, + request.url, + pod['pod_name'], + s_type=cons.ST_CINDER) + + if s_ctx['b_url'] == '': + LOG.error(_LE("Bottom pod endpoint incorrect %s") % + pod['pod_name']) + return utils.format_cinder_error( + 500, _('Bottom pod endpoint incorrect')) + + b_headers = hclient.convert_header(t_release, b_release, + request.headers) + + t_metadata = kw['metadata'] + + # add or remove key-value in the request for diff. version + b_vol_req = hclient.convert_object(t_release, b_release, t_metadata, + res_type=cons.RT_VOl_METADATA) + + b_body = jsonutils.dumps({'metadata': b_vol_req}) + + resp = hclient.forward_req( + context, + 'POST', + b_headers, + s_ctx['b_url'], + b_body) + b_status = resp.status_code + b_body_ret = jsonutils.loads(resp.content) + + # convert response from the bottom pod + # for different version. + response.status = b_status + if b_status == 200: + if b_body_ret.get('metadata') is not None: + b_metadata_ret = b_body_ret['metadata'] + + vol_ret = hclient.convert_object(b_release, t_release, + b_metadata_ret, + res_type=cons. + RT_VOl_METADATA) + + return {'metadata': vol_ret} + + return b_body_ret + + @expose(generic=True, template='json') + def get_one(self): + """Get all metadata associated with a volume.""" + context = t_context.extract_context_from_environ() + + t_release = cons.R_MITAKA + b_release = cons.R_MITAKA + + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) + + try: + s_ctx = hclient.get_res_routing_ref(context, self.volume_id, + request.url, cons.ST_CINDER) + if not s_ctx: + return utils.format_cinder_error( + 500, _('Fail to find resource')) + except Exception as e: + LOG.exception(_LE('Fail to get metadata for a volume:' + '%(volume_id)s' + '%(exception)s'), + {'volume_id': self.volume_id, + 'exception': e}) + return utils.format_cinder_error(500, _('Fail to get metadata')) + + if s_ctx['b_url'] == '': + return utils.format_cinder_error( + 500, _('Bottom pod endpoint incorrect')) + + resp = hclient.forward_req(context, 'GET', + b_headers, + s_ctx['b_url'], + request.body) + + b_body_ret = jsonutils.loads(resp.content) + + b_status = resp.status_code + response.status = b_status + if b_status == 200: + if b_body_ret.get('metadata') is not None: + b_metadata_ret = b_body_ret['metadata'] + vol_ret = hclient.convert_object(b_release, t_release, + b_metadata_ret, + res_type=cons. + RT_VOl_METADATA) + return {'metadata': vol_ret} + + return b_body_ret + + @expose(generic=True, template='json') + def put(self, **kw): + """Update volume metadata. + + :param kw: dictionary of values to be updated + :returns: updated volume type + """ + context = t_context.extract_context_from_environ() + + if 'metadata' not in kw: + return utils.format_cinder_error( + 400, _("Missing required element 'metadata' in " + "request body.")) + + t_release = cons.R_MITAKA + b_release = cons.R_MITAKA + + try: + s_ctx = hclient.get_res_routing_ref(context, self.volume_id, + request.url, cons.ST_CINDER) + if not s_ctx: + return utils.format_cinder_error( + 404, _('Resource not found')) + except Exception as e: + LOG.exception(_LE('Fail to update metadata for a volume: ' + '%(volume_id)s' + '%(exception)s'), + {'volume_id': self.volume_id, + 'exception': e}) + return utils.format_cinder_error( + 500, _('Fail to update metadata')) + + if s_ctx['b_url'] == '': + return utils.format_cinder_error( + 500, _('Bottom pod endpoint incorrect')) + + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) + + t_metadata = kw['metadata'] + + # add or remove key/value in the request for diff. version + b_vol_req = hclient.convert_object(t_release, b_release, t_metadata, + res_type=cons.RT_VOl_METADATA) + + b_body = jsonutils.dumps({'metadata': b_vol_req}) + + resp = hclient.forward_req(context, 'PUT', + b_headers, + s_ctx['b_url'], + b_body) + + b_status = resp.status_code + b_body_ret = jsonutils.loads(resp.content) + response.status = b_status + + if b_status == 200: + if b_body_ret.get('metadata') is not None: + b_metadata_ret = b_body_ret['metadata'] + vol_ret = hclient.convert_object(b_release, t_release, + b_metadata_ret, + res_type=cons. + RT_VOl_METADATA) + return {'metadata': vol_ret} + + return b_body_ret + + @expose(generic=True, template='json') + def delete(self, key): + """Delete the given metadata item from a volume.""" + context = t_context.extract_context_from_environ() + + t_release = cons.R_MITAKA + b_release = cons.R_MITAKA + + try: + s_ctx = hclient.get_res_routing_ref(context, self.volume_id, + request.url, cons.ST_CINDER) + if not s_ctx: + return utils.format_cinder_error( + 404, _('Fail to find resource')) + except Exception as e: + LOG.exception(_LE('Fail to delete metadata from a volume: ' + '%(volume_id)s' + '%(exception)s'), + {'volume_id': self.volume_id, + 'exception': e}) + return utils.format_cinder_error( + 500, _('Fail to delete metadata')) + + if s_ctx['b_url'] == '': + return utils.format_cinder_error( + 500, _('Bottom pod endpoint incorrect')) + + b_headers = hclient.convert_header(t_release, + b_release, + request.headers) + + resp = hclient.forward_req(context, 'DELETE', + b_headers, + s_ctx['b_url'], + request.body) + + response.status = resp.status_code + + # don't remove the resource routing for delete is async. operation + # remove the routing when query is executed but not found + # No content in the resp actually + return response diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index 38d5341..e7ad95e 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -26,6 +26,7 @@ ST_GLANCE = 'glance' # resource_type RT_SERVER = 'server' RT_VOLUME = 'volume' +RT_VOl_METADATA = 'volume_metadata' RT_BACKUP = 'backup' RT_SNAPSHOT = 'snapshot' RT_NETWORK = 'network' diff --git a/tricircle/common/httpclient.py b/tricircle/common/httpclient.py index 5d708c8..18d0cfa 100644 --- a/tricircle/common/httpclient.py +++ b/tricircle/common/httpclient.py @@ -19,11 +19,18 @@ import urlparse from requests import Request from requests import Session +from oslo_log import log as logging + from tricircle.common import client from tricircle.common import constants as cons +from tricircle.common.i18n import _LE +from tricircle.common import utils from tricircle.db import api as db_api +LOG = logging.getLogger(__name__) + + # the url could be endpoint registered in the keystone # or url sent to tricircle service, which is stored in # pecan.request.url @@ -147,3 +154,37 @@ def forward_req(context, action, b_headers, b_url, b_body): timeout=60) return resp + + +def get_res_routing_ref(context, _id, t_url, s_type): + """Get the service context according to resource routing. + + :param _id: the top id of resource + :param t_url: request url + :param s_type: service type + :returns: service context + """ + pod = utils.get_pod_by_top_id(context, _id) + + if not pod: + return None + + pod_name = pod['pod_name'] + + s_ctx = get_pod_service_ctx(context, t_url, pod_name, + s_type=s_type) + + if s_ctx['b_url'] == '': + LOG.error(_LE("bottom pod endpoint incorrect %s") % + pod_name) + + return s_ctx + + +def convert_header(from_release, to_release, header): + return header + + +def convert_object(from_release, to_release, res_object, + res_type=cons.RT_VOLUME): + return res_object diff --git a/tricircle/common/utils.py b/tricircle/common/utils.py index c99cb1d..7315c8e 100644 --- a/tricircle/common/utils.py +++ b/tricircle/common/utils.py @@ -17,8 +17,14 @@ import six import pecan +from oslo_log import log as logging + +from tricircle.common import constants as cons import tricircle.common.exceptions as t_exceptions from tricircle.common.i18n import _ +import tricircle.db.api as db_api + +LOG = logging.getLogger(__name__) def get_import_path(cls): @@ -143,3 +149,19 @@ def format_nova_error(code, message, error_type=None): def format_cinder_error(code, message, error_type=None): return format_error(code, message, error_type) + + +def get_pod_by_top_id(context, _id): + """Get pod resource from pod table . + + :param _id: the top id of resource + :returns: pod resource + """ + mappings = db_api.get_bottom_mappings_by_top_id( + context, _id, + cons.RT_VOLUME) + + if not mappings or len(mappings) != 1: + return None + + return mappings[0][0]