Browse Source
1. What is the problem? Nova API-GW and Cinder API-GW code are still in the repository of the Tricircle project. 2. What is the solution to the problem? According to the blueprint for the Tricircle splitting: https://blueprints.launchpad.net/tricircle/+spec/make-tricircle-dedicated-for-networking-automation-across-neutron Nova API-GW and Cinder API-GW related code should be removed from the repository of the Tricircle project. 3. What the features need to be implemented to the Tricircle to realize the solution? No new features. Change-Id: I152bab9c0a7a114f0361a69a3ab2d7037731434fchanges/82/384182/3
66 changed files with 0 additions and 10343 deletions
@ -1,63 +0,0 @@
|
||||
# Copyright 2015 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. |
||||
|
||||
# Much of this module is based on the work of the Ironic team |
||||
# see http://git.openstack.org/cgit/openstack/ironic/tree/ironic/cmd/api.py |
||||
|
||||
import logging as std_logging |
||||
import sys |
||||
|
||||
from oslo_config import cfg |
||||
from oslo_log import log as logging |
||||
from oslo_service import wsgi |
||||
|
||||
from tricircle.common import config |
||||
from tricircle.common.i18n import _LI |
||||
from tricircle.common.i18n import _LW |
||||
from tricircle.common import restapp |
||||
|
||||
from tricircle.cinder_apigw import app |
||||
|
||||
CONF = cfg.CONF |
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
|
||||
def main(): |
||||
config.init(app.common_opts, sys.argv[1:]) |
||||
application = app.setup_app() |
||||
|
||||
host = CONF.bind_host |
||||
port = CONF.bind_port |
||||
workers = CONF.api_workers |
||||
|
||||
if workers < 1: |
||||
LOG.warning(_LW("Wrong worker number, worker = %(workers)s"), workers) |
||||
workers = 1 |
||||
|
||||
LOG.info(_LI("Cinder_APIGW on http://%(host)s:%(port)s with %(workers)s"), |
||||
{'host': host, 'port': port, 'workers': workers}) |
||||
|
||||
service = wsgi.Server(CONF, 'Tricircle Cinder_APIGW', |
||||
application, host, port) |
||||
restapp.serve(service, CONF, workers) |
||||
|
||||
LOG.info(_LI("Configuration:")) |
||||
CONF.log_opt_values(LOG, std_logging.INFO) |
||||
|
||||
restapp.wait() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -1,68 +0,0 @@
|
||||
# Copyright 2015 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. |
||||
|
||||
# Much of this module is based on the work of the Ironic team |
||||
# see http://git.openstack.org/cgit/openstack/ironic/tree/ironic/cmd/api.py |
||||
|
||||
import eventlet |
||||
|
||||
if __name__ == "__main__": |
||||
eventlet.monkey_patch() |
||||
|
||||
import logging as std_logging |
||||
import sys |
||||
|
||||
from oslo_config import cfg |
||||
from oslo_log import log as logging |
||||
from oslo_service import wsgi |
||||
|
||||
from tricircle.common import config |
||||
from tricircle.common.i18n import _LI |
||||
from tricircle.common.i18n import _LW |
||||
from tricircle.common import restapp |
||||
|
||||
from tricircle.nova_apigw import app |
||||
|
||||
CONF = cfg.CONF |
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
|
||||
def main(): |
||||
config.init(app.common_opts, sys.argv[1:]) |
||||
application = app.setup_app() |
||||
|
||||
host = CONF.bind_host |
||||
port = CONF.bind_port |
||||
workers = CONF.api_workers |
||||
|
||||
if workers < 1: |
||||
LOG.warning(_LW("Wrong worker number, worker = %(workers)s"), workers) |
||||
workers = 1 |
||||
|
||||
LOG.info(_LI("Nova_APIGW on http://%(host)s:%(port)s with %(workers)s"), |
||||
{'host': host, 'port': port, 'workers': workers}) |
||||
|
||||
service = wsgi.Server(CONF, 'Tricircle Nova_APIGW', |
||||
application, host, port) |
||||
restapp.serve(service, CONF, workers) |
||||
|
||||
LOG.info(_LI("Configuration:")) |
||||
CONF.log_opt_values(LOG, std_logging.INFO) |
||||
|
||||
restapp.wait() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -1,16 +0,0 @@
|
||||
[DEFAULT] |
||||
output_file = etc/cinder_apigw.conf.sample |
||||
wrap_width = 79 |
||||
namespace = tricircle.cinder_apigw |
||||
namespace = tricircle.common |
||||
namespace = tricircle.db |
||||
namespace = oslo.log |
||||
namespace = oslo.messaging |
||||
namespace = oslo.policy |
||||
namespace = oslo.service.periodic_task |
||||
namespace = oslo.service.service |
||||
namespace = oslo.service.sslutils |
||||
namespace = oslo.db |
||||
namespace = oslo.middleware |
||||
namespace = oslo.concurrency |
||||
namespace = keystonemiddleware.auth_token |
@ -1,16 +0,0 @@
|
||||
[DEFAULT] |
||||
output_file = etc/nova_apigw.conf.sample |
||||
wrap_width = 79 |
||||
namespace = tricircle.nova_apigw |
||||
namespace = tricircle.common |
||||
namespace = tricircle.db |
||||
namespace = oslo.log |
||||
namespace = oslo.messaging |
||||
namespace = oslo.policy |
||||
namespace = oslo.service.periodic_task |
||||
namespace = oslo.service.service |
||||
namespace = oslo.service.sslutils |
||||
namespace = oslo.db |
||||
namespace = oslo.middleware |
||||
namespace = oslo.concurrency |
||||
namespace = keystonemiddleware.auth_token |
@ -1,76 +0,0 @@
|
||||
# Copyright (c) 2015 Huawei, Tech. 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 tricircle.common.i18n import _ |
||||
from tricircle.common import restapp |
||||
|
||||
|
||||
common_opts = [ |
||||
cfg.IPOpt('bind_host', default='0.0.0.0', |
||||
help=_("The host IP to bind to")), |
||||
cfg.PortOpt('bind_port', default=19997, |
||||
help=_("The port to bind to")), |
||||
cfg.IntOpt('api_workers', default=1, |
||||
help=_("number of api workers")), |
||||
cfg.StrOpt('api_extensions_path', default="", |
||||
help=_("The path for API extensions")), |
||||
cfg.StrOpt('auth_strategy', default='keystone', |
||||
help=_("The type of authentication to use")), |
||||
cfg.BoolOpt('allow_bulk', default=True, |
||||
help=_("Allow the usage of the bulk API")), |
||||
cfg.BoolOpt('allow_pagination', default=False, |
||||
help=_("Allow the usage of the pagination")), |
||||
cfg.BoolOpt('allow_sorting', default=False, |
||||
help=_("Allow the usage of the sorting")), |
||||
cfg.StrOpt('pagination_max_limit', default="-1", |
||||
help=_("The maximum number of items returned in a single " |
||||
"response, value was 'infinite' or negative integer " |
||||
"means no limit")), |
||||
] |
||||
|
||||
|
||||
def setup_app(*args, **kwargs): |
||||
config = { |
||||
'server': { |
||||
'port': cfg.CONF.bind_port, |
||||
'host': cfg.CONF.bind_host |
||||
}, |
||||
'app': { |
||||
'root': 'tricircle.cinder_apigw.controllers.root.RootController', |
||||
'modules': ['tricircle.cinder_apigw'], |
||||
'errors': { |
||||
400: '/error', |
||||
'__force_dict__': True |
||||
} |
||||
} |
||||
} |
||||
pecan_config = pecan.configuration.conf_from_dict(config) |
||||
|
||||
# app_hooks = [], hook collection will be put here later |
||||
|
||||
app = pecan.make_app( |
||||
pecan_config.app.root, |
||||
debug=False, |
||||
wrap_app=restapp.auth_app, |
||||
force_canonical=False, |
||||
hooks=[], |
||||
guess_content_type_from_ext=True |
||||
) |
||||
|
||||
return app |
@ -1,136 +0,0 @@
|
||||
# Copyright (c) 2015 Huawei Tech. 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 |
||||
|
||||
import oslo_log.log as logging |
||||
|
||||
from tricircle.cinder_apigw.controllers import volume |
||||
from tricircle.cinder_apigw.controllers import volume_actions |
||||
from tricircle.cinder_apigw.controllers import volume_metadata |
||||
from tricircle.cinder_apigw.controllers import volume_type |
||||
|
||||
|
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
|
||||
class RootController(object): |
||||
|
||||
@pecan.expose() |
||||
def _lookup(self, version, *remainder): |
||||
if version == 'v2': |
||||
return V2Controller(), remainder |
||||
|
||||
@pecan.expose(generic=True, template='json') |
||||
def index(self): |
||||
return { |
||||
"versions": [ |
||||
{ |
||||
"status": "CURRENT", |
||||
"updated": "2012-11-21T11:33:21Z", |
||||
"id": "v2.0", |
||||
"links": [ |
||||
{ |
||||
"href": pecan.request.application_url + "/v2/", |
||||
"rel": "self" |
||||
} |
||||
] |
||||
} |
||||
] |
||||
} |
||||
|
||||
@index.when(method='POST') |
||||
@index.when(method='PUT') |
||||
@index.when(method='DELETE') |
||||
@index.when(method='HEAD') |
||||
@index.when(method='PATCH') |
||||
def not_supported(self): |
||||
pecan.abort(405) |
||||
|
||||
|
||||
class V2Controller(object): |
||||
|
||||
_media_type1 = "application/vnd.openstack.volume+xml;version=1" |
||||
_media_type2 = "application/vnd.openstack.volume+json;version=1" |
||||
|
||||
def __init__(self): |
||||
|
||||
self.resource_controller = { |
||||
'volumes': volume.VolumeController, |
||||
'types': volume_type.VolumeTypeController |
||||
} |
||||
|
||||
self.volumes_sub_controller = { |
||||
'metadata': volume_metadata.VolumeMetaDataController, |
||||
'action': volume_actions.VolumeActionController, |
||||
} |
||||
|
||||
@pecan.expose() |
||||
def _lookup(self, tenant_id, *remainder): |
||||
if not remainder: |
||||
pecan.abort(404) |
||||
return |
||||
resource = remainder[0] |
||||
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') |
||||
def index(self): |
||||
return { |
||||
"version": { |
||||
"status": "CURRENT", |
||||
"updated": "2012-11-21T11:33:21Z", |
||||
"media-types": [ |
||||
{ |
||||
"base": "application/xml", |
||||
"type": self._media_type1 |
||||
}, |
||||
{ |
||||
"base": "application/json", |
||||
"type": self._media_type2 |
||||
} |
||||
], |
||||
"id": "v2.0", |
||||
"links": [ |
||||
{ |
||||
"href": pecan.request.application_url + "/v2/", |
||||
"rel": "self" |
||||
}, |
||||
{ |
||||
"href": "http://docs.openstack.org/", |
||||
"type": "text/html", |
||||
"rel": "describedby" |
||||
} |
||||
] |
||||
} |
||||
} |
||||
|
||||
@index.when(method='POST') |
||||
@index.when(method='PUT') |
||||
@index.when(method='DELETE') |
||||
@index.when(method='HEAD') |
||||
@index.when(method='PATCH') |
||||
def not_supported(self): |
||||
pecan.abort(405) |
@ -1,387 +0,0 @@
|
||||
# Copyright (c) 2015 Huawei Tech. 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 urlparse |
||||
|
||||
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 az_ag |
||||
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.scheduler import filter_scheduler |
||||
from tricircle.common import utils |
||||
|
||||
import tricircle.db.api as db_api |
||||
from tricircle.db import core |
||||
from tricircle.db import models |
||||
|
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
|
||||
class VolumeController(rest.RestController): |
||||
|
||||
def __init__(self, tenant_id): |
||||
self.tenant_id = tenant_id |
||||
self.filter_scheduler = filter_scheduler.FilterScheduler() |
||||
|
||||
@expose(generic=True, template='json') |
||||
def post(self, **kw): |
||||
context = t_context.extract_context_from_environ() |
||||
|
||||
if 'volume' not in kw: |
||||
return utils.format_cinder_error( |
||||
400, _("Missing required element 'volume' in request body.")) |
||||
|
||||
az = kw['volume'].get('availability_zone', '') |
||||
pod, pod_az = self.filter_scheduler.select_destination( |
||||
context, az, self.tenant_id, pod_group='') |
||||
|
||||
if not pod: |
||||
LOG.error(_LE("Pod not configured or scheduling failure")) |
||||
return utils.format_cinder_error( |
||||
500, _('Pod not configured or scheduling failure')) |
||||
|
||||
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')) |
||||
|
||||
# TODO(joehuang): get release from pod configuration, |
||||
# to convert the content |
||||
# b_release = pod['release'] |
||||
# t_release = t_pod['release'] |
||||
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_vol = kw['volume'] |
||||
|
||||
# add or remove key-value in the request for diff. version |
||||
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 |
||||
b_vol_req['availability_zone'] = pod['pod_az_name'] |
||||
if b_vol_req['availability_zone'] == '': |
||||
b_vol_req.pop("availability_zone", None) |
||||
|
||||
b_body = jsonutils.dumps({'volume': b_vol_req}) |
||||
|
||||
resp = hclient.forward_req( |
||||
context, |
||||
'POST', |
||||
b_headers, |
||||
s_ctx['b_url'], |
||||
b_body) |
||||
b_status = resp.status_code |
||||
b_ret_body = jsonutils.loads(resp.content) |
||||
|
||||
# build routing and convert response from the bottom pod |
||||
# for different version. |
||||
response.status = b_status |
||||
if b_status == 202: |
||||
if b_ret_body.get('volume') is not None: |
||||
b_vol_ret = b_ret_body['volume'] |
||||
|
||||
try: |
||||
with context.session.begin(): |
||||
core.create_resource( |
||||
context, models.ResourceRouting, |
||||
{'top_id': b_vol_ret['id'], |
||||
'bottom_id': b_vol_ret['id'], |
||||
'pod_id': pod['pod_id'], |
||||
'project_id': self.tenant_id, |
||||
'resource_type': cons.RT_VOLUME}) |
||||
except Exception as e: |
||||
LOG.exception(_LE('Failed to create volume ' |
||||
'resource routing' |
||||
'top_id: %(top_id)s ,' |
||||
'bottom_id: %(bottom_id)s ,' |
||||
'pod_id: %(pod_id)s ,' |
||||
'%(exception)s '), |
||||
{'top_id': b_vol_ret['id'], |
||||
'bottom_id': b_vol_ret['id'], |
||||
'pod_id': pod['pod_id'], |
||||
'exception': e}) |
||||
return utils.format_cinder_error( |
||||
500, _('Failed to create volume resource routing')) |
||||
|
||||
ret_vol = hclient.convert_object(b_release, t_release, |
||||
b_vol_ret, |
||||
res_type=cons.RT_VOLUME) |
||||
|
||||
ret_vol['availability_zone'] = pod['az_name'] |
||||
|
||||
return {'volume': ret_vol} |
||||
|
||||
return b_ret_body |
||||
|
||||
@expose(generic=True, template='json') |
||||
def get_one(self, _id): |
||||
context = t_context.extract_context_from_environ() |
||||
|
||||
if _id == 'detail': |
||||
return {'volumes': self._get_all(context)} |
||||
|
||||
# TODO(joehuang): get the release of top and bottom |
||||
t_release = cons.R_MITAKA |
||||
b_release = cons.R_MITAKA |
||||
|
||||
b_headers = hclient.convert_header(t_release, |
||||
b_release, |
||||
request.headers) |
||||
|
||||
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) |
||||
|
||||
if s_ctx['b_url'] == '': |
||||
return utils.format_cinder_error( |
||||
404, _('Bottom Pod endpoint incorrect')) |
||||
|
||||
resp = hclient.forward_req(context, 'GET', |
||||
b_headers, |
||||
s_ctx['b_url'], |
||||
request.body) |
||||
|
||||
b_ret_body = jsonutils.loads(resp.content) |
||||
|
||||
b_status = resp.status_code |
||||
response.status = b_status |
||||
if b_status == 200: |
||||
if b_ret_body.get('volume') is not None: |
||||
b_vol_ret = b_ret_body['volume'] |
||||
ret_vol = hclient.convert_object(b_release, t_release, |
||||
b_vol_ret, |
||||
res_type=cons.RT_VOLUME) |
||||
|
||||
pod = utils.get_pod_by_top_id(context, _id) |
||||
if pod: |
||||
ret_vol['availability_zone'] = pod['az_name'] |
||||
|
||||
return {'volume': ret_vol} |
||||
|
||||
# resource not find but routing exist, remove the routing |
||||
if b_status == 404: |
||||
filters = [{'key': 'top_id', 'comparator': 'eq', 'value': _id}, |
||||
{'key': 'resource_type', |
||||
'comparator': 'eq', |
||||
'value': cons.RT_VOLUME}] |
||||
with context.session.begin(): |
||||
core.delete_resources(context, |
||||
models.ResourceRouting, |
||||
filters) |
||||
return b_ret_body |
||||
|
||||
@expose(generic=True, template='json') |
||||
def get_all(self): |
||||
|
||||
# TODO(joehuang): here should return link instead, |
||||
# now combined with 'detail' |
||||
|
||||
context = t_context.extract_context_from_environ() |
||||
return {'volumes': self._get_all(context)} |
||||
|
||||
def _get_all(self, context): |
||||
|
||||
# TODO(joehuang): query optimization for pagination, sort, etc |
||||
ret = [] |
||||
pods = az_ag.list_pods_by_tenant(context, self.tenant_id) |
||||
for pod in pods: |
||||
if pod['pod_name'] == '': |
||||
continue |
||||
|
||||
query = urlparse.urlsplit(request.url).query |
||||
query_filters = urlparse.parse_qsl(query) |
||||
skip_pod = False |
||||
for k, v in query_filters: |
||||
if k == 'availability_zone' and v != pod['az_name']: |
||||
skip_pod = True |
||||
break |
||||
if skip_pod: |
||||
continue |
||||
|
||||
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']) |
||||
continue |
||||
|
||||
# TODO(joehuang): get the release of top and bottom |
||||
t_release = cons.R_MITAKA |
||||
b_release = cons.R_MITAKA |
||||
b_headers = hclient.convert_header(t_release, |
||||
b_release, |
||||
request.headers) |
||||
|
||||
resp = hclient.forward_req(context, 'GET', |
||||
b_headers, |
||||
s_ctx['b_url'], |
||||
request.body) |
||||
|
||||
if resp.status_code == 200: |
||||
|
||||
routings = db_api.get_bottom_mappings_by_tenant_pod( |
||||
context, self.tenant_id, |
||||
pod['pod_id'], cons.RT_VOLUME |
||||
) |
||||
|
||||
b_ret_body = jsonutils.loads(resp.content) |
||||
if b_ret_body.get('volumes'): |
||||
for vol in b_ret_body['volumes']: |
||||
|
||||
if not routings.get(vol['id']): |
||||
b_ret_body['volumes'].remove(vol) |
||||
continue |
||||
|
||||
vol['availability_zone'] = pod['az_name'] |
||||
|
||||
ret.extend(b_ret_body['volumes']) |
||||
return ret |
||||
|
||||
@expose(generic=True, template='json') |
||||
def put(self, _id, **kw): |
||||
context = t_context.extract_context_from_environ() |
||||
|
||||
# TODO(joehuang): Implement API multi-version compatibility |
||||
# currently _convert_header and _convert_object are both dummy |
||||
# functions and API versions are hard coded. After multi-version |
||||
# compatibility is implemented, API versions will be retrieved from |
||||
# top and bottom API server, also, _convert_header and _convert_object |
||||
# will do the real job to convert the request header and body |
||||
# according to the API versions. |
||||
t_release = cons.R_MITAKA |
||||
b_release = cons.R_MITAKA |
||||
|
||||
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) |
||||
|
||||
if s_ctx['b_url'] == '': |
||||
return utils.format_cinder_error( |
||||
404, _('Bottom Pod endpoint incorrect')) |
||||
|
||||
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 = hclient.convert_object(t_release, b_release, t_vol, |
||||
res_type=cons.RT_VOLUME) |
||||
|
||||
b_body = jsonutils.dumps({'volume': b_vol_req}) |
||||
|
||||
resp = hclient.forward_req(context, 'PUT', |
||||
b_headers, |
||||
s_ctx['b_url'], |
||||
b_body) |
||||
|
||||
b_status = resp.status_code |
||||
b_ret_body = jsonutils.loads(resp.content) |
||||
response.status = b_status |
||||
|
||||
if b_status == 200: |
||||
if b_ret_body.get('volume') is not None: |
||||
b_vol_ret = b_ret_body['volume'] |
||||
ret_vol = hclient.convert_object(b_release, t_release, |
||||
b_vol_ret, |
||||
res_type=cons.RT_VOLUME) |
||||
|
||||
pod = utils.get_pod_by_top_id(context, _id) |
||||
if pod: |
||||
ret_vol['availability_zone'] = pod['az_name'] |
||||
|
||||
return {'volume': ret_vol} |
||||
|
||||
# resource not found but routing exist, remove the routing |
||||
if b_status == 404: |
||||
filters = [{'key': 'top_id', 'comparator': 'eq', 'value': _id}, |
||||
{'key': 'resource_type', |
||||
'comparator': 'eq', |
||||
'value': cons.RT_VOLUME}] |
||||
with context.session.begin(): |
||||
core.delete_resources(context, |
||||
models.ResourceRouting, |
||||
filters) |
||||
return b_ret_body |
||||
|
||||
@expose(generic=True, template='json') |
||||
def delete(self, _id): |
||||
context = t_context.extract_context_from_environ() |
||||
|
||||
# TODO(joehuang): get the release of top and bottom |
||||
t_release = cons.R_MITAKA |
||||
b_release = cons.R_MITAKA |
||||
|
||||
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) |
||||
|
||||
if s_ctx['b_url'] == '': |
||||
return utils.format_cinder_error( |
||||
404, _('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 find |
||||
# No content in the resp actually |
||||
return response |
@ -1,244 +0,0 @@
|
||||
# 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. |
||||
|
||||
import pecan |
||||
from pecan import expose |
||||
from pecan import rest |
||||
|
||||
from oslo_log import log as logging |
||||
|
||||
import tricircle.common.client as t_client |
||||
from tricircle.common import constants |
||||
import tricircle.common.context as t_context |
||||
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 VolumeActionController(rest.RestController): |
||||
|
||||
def __init__(self, project_id, volume_id): |
||||
self.project_id = project_id |
||||
self.volume_id = volume_id |
||||
self.clients = {constants.TOP: t_client.Client()} |
||||
self.handle_map = { |
||||
'os-attach': self._attach, |
||||
'os-extend': self._extend, |
||||
'os-reset_status': self._reset_status, |
||||
'os-set_image_metadata': self._set_image_metadata, |
||||
'os-unset_image_metadata': self._unset_image_metadata, |
||||
'os-show_image_metadata': self._show_image_metadata, |
||||
'os-force_detach': self._force_detach |
||||
} |
||||
|
||||
def _get_client(self, pod_name=constants.TOP): |
||||
if pod_name not in self.clients: |
||||
self.clients[pod_name] = t_client.Client(pod_name) |
||||
return self.clients[pod_name] |
||||
|
||||
def _action(self, context, pod_name, action, info=None, **kwargs): |
||||
"""Perform a volume "action". |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param action: volume action name. |
||||
:param info: action parameters body. |
||||
""" |
||||
body = {action: info} |
||||
url = '/volumes/%s/action' % self.volume_id |
||||
api = self._get_client(pod_name).get_native_client('volume', context) |
||||
return api.client.post(url, body=body) |
||||
|
||||
def _attach(self, context, pod_name, kw): |
||||
"""Add attachment metadata. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
try: |
||||
mountpoint = None |
||||
if 'mountpoint' in kw['os-attach']: |
||||
mountpoint = kw['os-attach']['mountpoint'] |
||||
body = {'mountpoint': mountpoint} |
||||
instance_uuid = None |
||||
if 'instance_uuid' in kw['os-attach']: |
||||
instance_uuid = kw['os-attach']['instance_uuid'] |
||||
host_name = None |
||||
if 'host_name' in kw['os-attach']: |
||||
host_name = kw['os-attach']['host_name'] |
||||
except (KeyError, ValueError, TypeError): |
||||
msg = _('The server could not comply with the request since ' |
||||
'it is either malformed or otherwise incorrect.') |
||||
return utils.format_cinder_error(400, msg) |
||||
|
||||
if instance_uuid is not None: |
||||
body.update({'instance_uuid': instance_uuid}) |
||||
if host_name is not None: |
||||
body.update({'host_name': host_name}) |
||||
return self._action(context, pod_name, 'os-attach', body) |
||||
|
||||
def _extend(self, context, pod_name, kw): |
||||
"""Extend the size of the specified volume. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
try: |
||||
new_size = int(kw['os-extend']['new_size']) |
||||
except (KeyError, ValueError, TypeError): |
||||
msg = _("New volume size must be specified as an integer.") |
||||
return utils.format_cinder_error(400, msg) |
||||
return self._action(context, pod_name, 'os-extend', |
||||
{'new_size': new_size}) |
||||
|
||||
def _force_detach(self, context, pod_name, kw): |
||||
"""Forces a volume to detach |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
body = kw['os-force_detach'] |
||||
return self._action(context, pod_name, 'os-force_detach', body) |
||||
|
||||
def _reset_status(self, context, pod_name, kw): |
||||
"""Update the provided volume with the provided state. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
try: |
||||
status = None |
||||
if 'status' in kw['os-reset_status']: |
||||
status = kw['os-reset_status']['status'] |
||||
attach_status = None |
||||
if 'attach_status' in kw['os-reset_status']: |
||||
attach_status = kw['os-reset_status']['attach_status'] |
||||
migration_status = None |
||||
if 'migration_status' in kw['os-reset_status']: |
||||
migration_status = kw['os-reset_status']['migration_status'] |
||||
except (TypeError, KeyError, ValueError): |
||||
msg = _('The server has either erred or is incapable of ' |
||||
'performing the requested operation.') |
||||
return utils.format_cinder_error(500, msg) |
||||
|
||||
body = {'status': status} if status else {} |
||||
if attach_status: |
||||
body.update({'attach_status': attach_status}) |
||||
if migration_status: |
||||
body.update({'migration_status': migration_status}) |
||||
return self._action(context, pod_name, 'os-reset_status', body) |
||||
|
||||
def _set_image_metadata(self, context, pod_name, kw): |
||||
"""Set a volume's image metadata. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
try: |
||||
metadata = kw['os-set_image_metadata']['metadata'] |
||||
except (KeyError, TypeError): |
||||
msg = _("Malformed request body.") |
||||
return utils.format_cinder_error(400, msg) |
||||
return self._action(context, pod_name, 'os-set_image_metadata', |
||||
{'metadata': metadata}) |
||||
|
||||
def _unset_image_metadata(self, context, pod_name, kw): |
||||
"""Unset specified keys from volume's image metadata. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
try: |
||||
key = kw['os-unset_image_metadata']['key'] |
||||
except (KeyError, TypeError): |
||||
msg = _("Malformed request body.") |
||||
return utils.format_cinder_error(400, msg) |
||||
return self._action( |
||||
context, pod_name, 'os-unset_image_metadata', {'key': key}) |
||||
|
||||
def _show_image_metadata(self, context, pod_name, kw): |
||||
"""Show a volume's image metadata. |
||||
|
||||
:param pod_name: the bottom pod name. |
||||
:param kw: request body. |
||||
""" |
||||
return self._action(context, pod_name, 'os-show_image_metadata') |
||||
|
||||
@expose(generic=True, template='json') |
||||
def post(self, **kw): |
||||
context = t_context.extract_context_from_environ() |
||||
|
||||
action_handle = None |
||||
action_type = None |
||||
for _type in self.handle_map: |
||||
if _type in kw: |
||||
action_handle = self.handle_map[_type] |
||||
action_type = _type |
||||
if not action_handle: |
||||
return utils.format_cinder_error( |
||||
400, _('Volume action not supported')) |
||||
|
||||
volume_mappings = db_api.get_bottom_mappings_by_top_id( |
||||
context, self.volume_id, constants.RT_VOLUME) |
||||
if not volume_mappings: |
||||
return utils.format_cinder_error( |
||||
404, _('Volume %(volume_id)s could not be found.') % { |
||||
'volume_id': self.volume_id |
||||
}) |
||||
|
||||
pod_name = volume_mappings[0][0]['pod_name'] |
||||
|
||||
if action_type == 'os-attach': |
||||
instance_uuid = kw['os-attach'].get('instance_uuid') |
||||
if instance_uuid is not None: |
||||
server_mappings = db_api.get_bottom_mappings_by_top_id( |
||||
context, instance_uuid, constants.RT_SERVER) |
||||
if not server_mappings: |
||||
return utils.format_cinder_error( |
||||
404, _('Server not found')) |
||||
server_pod_name = server_mappings[0][0]['pod_name'] |
||||
if server_pod_name != pod_name: |
||||
LOG.error(_LE('Server %(server)s is in pod %(server_pod)s' |
||||
'and volume %(volume)s is in pod' |
||||
'%(volume_pod)s, which ' |
||||
'are not the same.'), |
||||
{'server': instance_uuid, |
||||
'server_pod': server_pod_name, |
||||
'volume': self.volume_id, |
||||
'volume_pod': pod_name}) |
||||
return utils.format_cinder_error( |
||||
400, _('Server and volume not in the same pod')) |
||||
|
||||
try: |
||||
resp, body = action_handle(context, pod_name, kw) |
||||
pecan.response.status = resp.status_code |
||||
if not body: |
||||
return pecan.response |
||||
else: |
||||
return body |
||||
except Exception as e: |
||||
code = 500 |
||||
message = _('Action %(action)s on volume %(volume_id)s fails') % { |
||||
'action': action_type, |
||||
'volume_id': self.volume_id} |
||||
if hasattr(e, 'code'): |
||||
code = e.code |
||||
ex_message = str(e) |
||||
if ex_message: |
||||
message = ex_message |
||||
LOG.error(message) |
||||
return utils.format_cinder_error(code, message) |
@ -1,287 +0,0 @@
|
||||
# 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 |