Merge "Implement volume metadata basic operation"

This commit is contained in:
Jenkins 2016-06-25 10:24:49 +00:00 committed by Gerrit Code Review
commit 122d2b1866
6 changed files with 398 additions and 74 deletions

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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]