Browse Source

Remove API-GW related code from the Tricricle

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: I152bab9c0a7a114f0361a69a3ab2d7037731434f
changes/82/384182/3
joehuang 5 years ago
parent
commit
467f8fcb47
66 changed files with 0 additions and 10343 deletions
  1. +0
    -63
      cmd/cinder_apigw.py
  2. +0
    -68
      cmd/nova_apigw.py
  3. +0
    -16
      etc/cinder_apigw-cfg-gen.conf
  4. +0
    -16
      etc/nova_apigw-cfg-gen.conf
  5. +0
    -8
      setup.cfg
  6. +0
    -2
      tox.ini
  7. +0
    -0
      tricircle/cinder_apigw/__init__.py
  8. +0
    -76
      tricircle/cinder_apigw/app.py
  9. +0
    -0
      tricircle/cinder_apigw/controllers/__init__.py
  10. +0
    -136
      tricircle/cinder_apigw/controllers/root.py
  11. +0
    -387
      tricircle/cinder_apigw/controllers/volume.py
  12. +0
    -244
      tricircle/cinder_apigw/controllers/volume_actions.py
  13. +0
    -287
      tricircle/cinder_apigw/controllers/volume_metadata.py
  14. +0
    -286
      tricircle/cinder_apigw/controllers/volume_type.py
  15. +0
    -22
      tricircle/cinder_apigw/opts.py
  16. +0
    -0
      tricircle/common/scheduler/__init__.py
  17. +0
    -31
      tricircle/common/scheduler/driver.py
  18. +0
    -58
      tricircle/common/scheduler/filter_scheduler.py
  19. +0
    -0
      tricircle/common/scheduler/filters/__init__.py
  20. +0
    -31
      tricircle/common/scheduler/filters/base_filters.py
  21. +0
    -23
      tricircle/common/scheduler/filters/bottom_pod_filter.py
  22. +0
    -109
      tricircle/common/scheduler/pod_manager.py
  23. +0
    -0
      tricircle/nova_apigw/__init__.py
  24. +0
    -83
      tricircle/nova_apigw/app.py
  25. +0
    -0
      tricircle/nova_apigw/controllers/__init__.py
  26. +0
    -173
      tricircle/nova_apigw/controllers/action.py
  27. +0
    -141
      tricircle/nova_apigw/controllers/aggregate.py
  28. +0
    -217
      tricircle/nova_apigw/controllers/flavor.py
  29. +0
    -197
      tricircle/nova_apigw/controllers/image.py
  30. +0
    -120
      tricircle/nova_apigw/controllers/micro_versions.py
  31. +0
    -50
      tricircle/nova_apigw/controllers/network.py
  32. +0
    -269
      tricircle/nova_apigw/controllers/quota_sets.py
  33. +0
    -166
      tricircle/nova_apigw/controllers/root.py
  34. +0
    -82
      tricircle/nova_apigw/controllers/root_versions.py
  35. +0
    -680
      tricircle/nova_apigw/controllers/server.py
  36. +0
    -72
      tricircle/nova_apigw/controllers/server_ips.py
  37. +0
    -259
      tricircle/nova_apigw/controllers/volume.py
  38. +0
    -22
      tricircle/nova_apigw/opts.py
  39. +0
    -7
      tricircle/tempestplugin/post_test_hook.sh
  40. +0
    -2
      tricircle/tempestplugin/pre_test_hook.sh
  41. +0
    -601
      tricircle/tempestplugin/tempest_compute.sh
  42. +0
    -326
      tricircle/tempestplugin/tempest_volume.sh
  43. +0
    -0
      tricircle/tests/functional/cinder_apigw/__init__.py
  44. +0
    -0
      tricircle/tests/functional/cinder_apigw/controllers/__init__.py
  45. +0
    -172
      tricircle/tests/functional/cinder_apigw/controllers/test_root.py
  46. +0
    -629
      tricircle/tests/functional/cinder_apigw/controllers/test_volume.py
  47. +0
    -0
      tricircle/tests/functional/nova_apigw/__init__.py
  48. +0
    -0
      tricircle/tests/functional/nova_apigw/controllers/__init__.py
  49. +0
    -447
      tricircle/tests/functional/nova_apigw/controllers/test_microversion.py
  50. +0
    -479
      tricircle/tests/functional/nova_apigw/controllers/test_quota_sets.py
  51. +0
    -174
      tricircle/tests/functional/nova_apigw/controllers/test_root.py
  52. +0
    -0
      tricircle/tests/unit/cinder_apigw/__init__.py
  53. +0
    -0
      tricircle/tests/unit/cinder_apigw/controllers/__init__.py
  54. +0
    -271
      tricircle/tests/unit/cinder_apigw/controllers/test_volume.py
  55. +0
    -306
      tricircle/tests/unit/cinder_apigw/controllers/test_volume_actions.py
  56. +0
    -0
      tricircle/tests/unit/common/scheduler/__init__.py
  57. +0
    -141
      tricircle/tests/unit/common/scheduler/test_filter_scheduler.py
  58. +0
    -141
      tricircle/tests/unit/common/scheduler/test_pod_manager.py
  59. +0
    -0
      tricircle/tests/unit/nova_apigw/__init__.py
  60. +0
    -0
      tricircle/tests/unit/nova_apigw/controllers/__init__.py
  61. +0
    -473
      tricircle/tests/unit/nova_apigw/controllers/test_action.py
  62. +0
    -62
      tricircle/tests/unit/nova_apigw/controllers/test_aggregate.py
  63. +0
    -47
      tricircle/tests/unit/nova_apigw/controllers/test_flavor.py
  64. +0
    -1264
      tricircle/tests/unit/nova_apigw/controllers/test_server.py
  65. +0
    -103
      tricircle/tests/unit/nova_apigw/controllers/test_server_ips.py
  66. +0
    -304
      tricircle/tests/unit/nova_apigw/controllers/test_volume.py

+ 0
- 63
cmd/cinder_apigw.py View File

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

+ 0
- 68
cmd/nova_apigw.py View File

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

+ 0
- 16
etc/cinder_apigw-cfg-gen.conf View File

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

+ 0
- 16
etc/nova_apigw-cfg-gen.conf View File

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

+ 0
- 8
setup.cfg View File

@ -50,9 +50,6 @@ oslo.config.opts =
tricircle.common = tricircle.common.opts:list_opts
tricircle.db = tricircle.db.opts:list_opts
tricircle.network = tricircle.network.opts:list_opts
tricircle.nova_apigw = tricircle.nova_apigw.opts:list_opts
tricircle.cinder_apigw = tricircle.cinder_apigw.opts:list_opts
tricircle.xjob = tricircle.xjob.opts:list_opts
tempest.test_plugins =
@ -61,8 +58,3 @@ tempest.test_plugins =
tricircle.network.type_drivers =
local = tricircle.network.drivers.type_local:LocalTypeDriver
shared_vlan = tricircle.network.drivers.type_shared_vlan:SharedVLANTypeDriver
tricircle.common.schedulers =
pod_manager = tricircle.common.scheduler.pod_manager:PodManager
bottom_pod_filter = tricircle.common.scheduler.filters.bottom_pod_filter:BottomPodFilter
filter_scheduler = tricircle.common.scheduler.filter_scheduler:FilterScheduler

+ 0
- 2
tox.ini View File

@ -28,8 +28,6 @@ commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:genconfig]
commands = oslo-config-generator --config-file=etc/api-cfg-gen.conf
oslo-config-generator --config-file=etc/nova_apigw-cfg-gen.conf
oslo-config-generator --config-file=etc/cinder_apigw-cfg-gen.conf
oslo-config-generator --config-file=etc/xjob-cfg-gen.conf
oslo-config-generator --config-file=etc/tricircle_plugin-cfg-gen.conf


+ 0
- 0
tricircle/cinder_apigw/__init__.py View File


+ 0
- 76
tricircle/cinder_apigw/app.py View File

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

+ 0
- 0
tricircle/cinder_apigw/controllers/__init__.py View File


+ 0
- 136
tricircle/cinder_apigw/controllers/root.py View File

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

+ 0
- 387
tricircle/cinder_apigw/controllers/volume.py View File

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

+ 0
- 244
tricircle/cinder_apigw/controllers/volume_actions.py View File

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

+ 0
- 287
tricircle/cinder_apigw/controllers/volume_metadata.py View File

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

+ 0
- 286
tricircle/cinder_apigw/controllers/volume_type.py View File

@ -1,286 +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
from oslo_utils import uuidutils
import tricircle.common.context as t_context
from tricircle.common import exceptions
from tricircle.common.i18n import _
from tricircle.common.i18n import _LE
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 VolumeTypeController(rest.RestController):
def __init__(self, tenant_id):
self.tenant_id = tenant_id
def _metadata_refs(self, metadata_dict, meta_class):
metadata_refs = []
if metadata_dict:
for k, v in metadata_dict.items():
metadata_ref = meta_class()
metadata_ref['key'] = k
metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
return metadata_refs
@expose(generic=True, template='json')
def post(self, **kw):
"""Creates volume types."""
context = t_context.extract_context_from_environ()
if not context.is_admin:
return utils.format_cinder_error(
403, _("Policy doesn't allow volume_extension:types_manage "
"to be performed."))
if 'volume_type' not in kw:
return utils.format_cinder_error(
400, _("Missing required element 'volume_type' in "
"request body."))
projects = []
if self.tenant_id is not None:
projects = [self.tenant_id]
vol_type = kw['volume_type']
name = vol_type.get('name', None)
description = vol_type.get('description')
specs = vol_type.get('extra_specs', {})
is_public = vol_type.pop('os-volume-type-access:is_public', True)
if name is None or len(name.strip()) == 0:
return utils.format_cinder_error(
400, _("Volume type name can not be empty."))
try:
utils.check_string_length(name, 'Type name',
min_len=1, max_len=255)
except exceptions.InvalidInput as e:
return utils.format_cinder_error(
400, e.message)
if description is not None:
try:
utils.check_string_length(description, 'Type description',
min_len=0, max_len=255)
except exceptions.InvalidInput as e:
return utils.format_cinder_error(400, e.message)
if not utils.is_valid_boolstr(is_public):
msg = _("Invalid value '%(is_public)s' for is_public. "
"Accepted values: True or False.") % {
'is_public': is_public}
return utils.format_cinder_error(400, msg)
vol_type['extra_specs'] = specs
vol_type['is_public'] = is_public
vol_type['id'] = uuidutils.generate_uuid()
session = core.get_session()
with session.begin():
try:
db_api.volume_type_get_by_name(context, vol_type['name'],
session)
return utils.format_cinder_error(
409, _("Volume Type %(id)s already exists.") % {
'id': vol_type['id']})
except exceptions.VolumeTypeNotFoundByName:
pass
try:
extra_specs = vol_type['extra_specs']
vol_type['extra_specs'] = \
self._metadata_refs(vol_type.get('extra_specs'),
models.VolumeTypeExtraSpecs)
volume_type_ref = models.VolumeTypes()
volume_type_ref.update(vol_type)
session.add(volume_type_ref)
for project in set(projects):
access_ref = models.VolumeTypeProjects()
access_ref.update({"volume_type_id": volume_type_ref.id,
"project_id": project})
access_ref.save(session=session)
except Exception as e:
LOG.exception(_LE('Fail to create volume type: %(name)s,'
'%(exception)s'),
{'name': vol_type['name'],
'exception': e})
return utils.format_cinder_error(
500, _('Fail to create volume type'))
vol_type['extra_specs'] = extra_specs
return {'volume_type': vol_type}
@expose(generic=True, template='json')
def get_one(self, _id):
"""Retrieves single volume type by id.
:param _id: id of volume type to be retrieved
:returns: retrieved volume type
"""
context = t_context.extract_context_from_environ()
try:
result = db_api.volume_type_get(context, _id)
except exceptions.VolumeTypeNotFound as e:
return utils.format_cinder_error(404, e.message)
except Exception as e:
LOG.exception(_LE('Volume type not found: %(id)s,'
'%(exception)s'),
{'id': _id,
'exception': e})
return utils.format_cinder_error(
404, _("Volume type %(id)s could not be found.") % {
'id': _id})
return {'volume_type': result}
@expose(generic=True, template='json')
def get_all(self):
"""Get all non-deleted volume_types."""
filters = {}
context = t_context.extract_context_from_environ()
if not context.is_admin:
# Only admin has query access to all volume types
filters['is_public'] = True
try:
list_result = db_api.volume_type_get_all(context,
list_result=True,
filters=filters)
except Exception as e:
LOG.exception(_LE('Fail to retrieve volume types: %(exception)s'),
{'exception': e})
return utils.format_cinder_error(500, e)
return {'volume_types': list_result}
@expose(generic=True, template='json')
def put(self, _id, **kw):
"""Update volume type by id.
:param _id: id of volume type to be updated
:param kw: dictionary of values to be updated
:returns: updated volume type
"""
context = t_context.extract_context_from_environ()
if not context.is_admin:
return utils.format_cinder_error(
403, _("Policy doesn't allow volume_extension:types_manage "
"to be performed."))
if 'volume_type' not in kw:
return utils.format_cinder_error(
400, _("Missing required element 'volume_type' in "
"request body."))
values = kw['volume_type']
name = values.get('name')
description = values.get('description')
is_public = values.get('os-volume-type-access:is_public')
# Name and description can not be both None.
# If name specified, name can not be empty.
if name and len(name.strip()) == 0:
return utils.format_cinder_error(
400, _("Volume type name can not be empty."))
if name is None and description is None and is_public is None:
msg = _("Specify volume type name, description, is_public or "
"a combination thereof.")
return utils.format_cinder_error(400, msg)
if is_public is not None and not utils.is_valid_boolstr(is_public):
msg = _(