diff options
| author | Vishvananda Ishaya <vishvananda@gmail.com> | 2012-10-23 19:30:48 -0700 |
|---|---|---|
| committer | Vishvananda Ishaya <vishvananda@gmail.com> | 2012-10-28 11:34:05 -0700 |
| commit | 7e2b93acc59dea81d52684f7f659fcff32507e14 (patch) | |
| tree | 11bf7e96a3835d0002f133742dd9e1702c7cc6c6 /nova/api | |
| parent | 4e449c0843150b785bc61e87599d05ff242a8f4a (diff) | |
removes the nova-volume code from nova
This removes the majority of the nova-volume code from the codebase.
It updates relevent config options to default to cinder. It updates a
number of existing tests that were depending on code that was removed.
A few things still need to be removed:
* volume/driver.py & volume/iscsi.py
These files are used by the libvirt volume driver tests. These
tests should be updated to mock the relevant calls.
* scheduler/simple.py & scheduler/multi.py
These files should no longer be necessary so they can be removed
in a subsequent patch
* exception.py cleanup
Once the above files are removed there are a number of unused
exceptions which can be removed
* database calls and database tables
The database calls have not been removed and the tables have not
been dropped. This can be done in a separate migration
* additional config options and nova.conf.sample
There may be a few extra config options that can be removed and the
conf sample can be regenerated
Implements bp delete-nova-volume
Change-Id: I0b540e54dbabd26901a7530035a38583bb521fda
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/compute/contrib/volumes.py | 7 | ||||
| -rw-r--r-- | nova/api/openstack/compute/contrib/volumetypes.py | 225 | ||||
| -rw-r--r-- | nova/api/openstack/volume/__init__.py | 68 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/__init__.py | 39 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/admin_actions.py | 129 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/image_create.py | 31 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/types_extra_specs.py | 149 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/types_manage.py | 91 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/volume_actions.py | 131 | ||||
| -rw-r--r-- | nova/api/openstack/volume/extensions.py | 34 | ||||
| -rw-r--r-- | nova/api/openstack/volume/snapshots.py | 185 | ||||
| -rw-r--r-- | nova/api/openstack/volume/types.py | 80 | ||||
| -rw-r--r-- | nova/api/openstack/volume/versions.py | 83 | ||||
| -rw-r--r-- | nova/api/openstack/volume/views/__init__.py | 16 | ||||
| -rw-r--r-- | nova/api/openstack/volume/views/types.py | 34 | ||||
| -rw-r--r-- | nova/api/openstack/volume/views/versions.py | 36 | ||||
| -rw-r--r-- | nova/api/openstack/volume/volumes.py | 364 |
17 files changed, 0 insertions, 1702 deletions
diff --git a/nova/api/openstack/compute/contrib/volumes.py b/nova/api/openstack/compute/contrib/volumes.py index 9940e3050..6eaa51079 100644 --- a/nova/api/openstack/compute/contrib/volumes.py +++ b/nova/api/openstack/compute/contrib/volumes.py @@ -29,7 +29,6 @@ from nova import flags from nova.openstack.common import log as logging from nova import utils from nova import volume -from nova.volume import volume_types LOG = logging.getLogger(__name__) @@ -227,12 +226,6 @@ class VolumeController(wsgi.Controller): vol = body['volume'] vol_type = vol.get('volume_type', None) - if vol_type: - try: - vol_type = volume_types.get_volume_type_by_name(context, - vol_type) - except exception.NotFound: - raise exc.HTTPNotFound() metadata = vol.get('metadata', None) diff --git a/nova/api/openstack/compute/contrib/volumetypes.py b/nova/api/openstack/compute/contrib/volumetypes.py deleted file mode 100644 index 036e3ff42..000000000 --- a/nova/api/openstack/compute/contrib/volumetypes.py +++ /dev/null @@ -1,225 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 OpenStack LLC. -# -# 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. - -""" The volume type & volume types extra specs extension""" - -from webob import exc - -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import db -from nova import exception -from nova.volume import volume_types - - -authorize = extensions.extension_authorizer('compute', 'volumetypes') - - -def make_voltype(elem): - elem.set('id') - elem.set('name') - extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - elem.append(extra_specs) - - -class VolumeTypeTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume_type', selector='volume_type') - make_voltype(root) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume_types') - elem = xmlutil.SubTemplateElement(root, 'volume_type', - selector='volume_types') - make_voltype(elem) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypesController(wsgi.Controller): - """ The volume types API controller for the OpenStack API """ - - @wsgi.serializers(xml=VolumeTypesTemplate) - def index(self, req): - """ Returns the list of volume types """ - context = req.environ['nova.context'] - authorize(context) - return {'volume_types': volume_types.get_all_types(context).values()} - - @wsgi.serializers(xml=VolumeTypeTemplate) - def create(self, req, body): - """Creates a new volume type.""" - context = req.environ['nova.context'] - authorize(context) - - if not self.is_valid_body(body, 'volume_type'): - raise exc.HTTPUnprocessableEntity() - - vol_type = body['volume_type'] - name = vol_type.get('name', None) - specs = vol_type.get('extra_specs', {}) - - if name is None or name == "": - raise exc.HTTPUnprocessableEntity() - - try: - volume_types.create(context, name, specs) - vol_type = volume_types.get_volume_type_by_name(context, name) - except exception.NotFound: - raise exc.HTTPNotFound() - - return {'volume_type': vol_type} - - @wsgi.serializers(xml=VolumeTypeTemplate) - def show(self, req, id): - """ Return a single volume type item """ - context = req.environ['nova.context'] - authorize(context) - - try: - vol_type = volume_types.get_volume_type(context, id) - except exception.NotFound: - raise exc.HTTPNotFound() - - return {'volume_type': vol_type} - - def delete(self, req, id): - """ Deletes an existing volume type """ - context = req.environ['nova.context'] - authorize(context) - - try: - vol_type = volume_types.get_volume_type(context, id) - volume_types.destroy(context, vol_type['name']) - except exception.NotFound: - raise exc.HTTPNotFound() - - -class VolumeTypeExtraSpecsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecTemplate(xmlutil.TemplateBuilder): - def construct(self): - tagname = xmlutil.Selector('key') - - def extraspec_sel(obj, do_raise=False): - # Have to extract the key and value for later use... - key, value = obj.items()[0] - return dict(key=key, value=value) - - root = xmlutil.TemplateElement(tagname, selector=extraspec_sel) - root.text = 'value' - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecsController(object): - """ The volume type extra specs API controller for the OpenStack API """ - - def _get_extra_specs(self, context, vol_type_id): - extra_specs = db.volume_type_extra_specs_get(context, vol_type_id) - specs_dict = {} - for key, value in extra_specs.iteritems(): - specs_dict[key] = value - return dict(extra_specs=specs_dict) - - def _check_body(self, body): - if body is None or body == "": - expl = _('No Request Body') - raise exc.HTTPBadRequest(explanation=expl) - - @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) - def index(self, req, vol_type_id): - """ Returns the list of extra specs for a given volume type """ - context = req.environ['nova.context'] - authorize(context) - return self._get_extra_specs(context, vol_type_id) - - @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) - def create(self, req, vol_type_id, body): - context = req.environ['nova.context'] - authorize(context) - self._check_body(body) - specs = body.get('extra_specs') - db.volume_type_extra_specs_update_or_create(context, - vol_type_id, - specs) - return body - - @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) - def update(self, req, vol_type_id, id, body): - context = req.environ['nova.context'] - authorize(context) - self._check_body(body) - if not id in body: - expl = _('Request body and URI mismatch') - raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: - expl = _('Request body contains too many items') - raise exc.HTTPBadRequest(explanation=expl) - db.volume_type_extra_specs_update_or_create(context, - vol_type_id, - body) - return body - - @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) - def show(self, req, vol_type_id, id): - """ Return a single extra spec item """ - context = req.environ['nova.context'] - authorize(context) - specs = self._get_extra_specs(context, vol_type_id) - if id in specs['extra_specs']: - return {id: specs['extra_specs'][id]} - else: - raise exc.HTTPNotFound() - - def delete(self, req, vol_type_id, id): - """ Deletes an existing extra spec """ - context = req.environ['nova.context'] - authorize(context) - db.volume_type_extra_specs_delete(context, vol_type_id, id) - - -class Volumetypes(extensions.ExtensionDescriptor): - """Volume types support""" - - name = "VolumeTypes" - alias = "os-volume-types" - namespace = "http://docs.openstack.org/compute/ext/volume_types/api/v1.1" - updated = "2011-08-24T00:00:00+00:00" - - def get_resources(self): - resources = [] - - res = extensions.ResourceExtension( - 'os-volume-types', - VolumeTypesController()) - resources.append(res) - - res = extensions.ResourceExtension('extra_specs', - VolumeTypeExtraSpecsController(), - parent=dict( - member_name='vol_type', - collection_name='os-volume-types')) - resources.append(res) - - return resources diff --git a/nova/api/openstack/volume/__init__.py b/nova/api/openstack/volume/__init__.py deleted file mode 100644 index 3aca5bebf..000000000 --- a/nova/api/openstack/volume/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -""" -WSGI middleware for OpenStack Volume API. -""" - -import nova.api.openstack -from nova.api.openstack.volume import extensions -from nova.api.openstack.volume import snapshots -from nova.api.openstack.volume import types -from nova.api.openstack.volume import versions -from nova.api.openstack.volume import volumes -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - - -class APIRouter(nova.api.openstack.APIRouter): - """ - Routes requests on the OpenStack API to the appropriate controller - and method. - """ - ExtensionManager = extensions.ExtensionManager - - def _setup_routes(self, mapper, ext_mgr, init_only): - if init_only is None or 'versions' in init_only: - self.resources['versions'] = versions.create_resource() - mapper.connect("versions", "/", - controller=self.resources['versions'], - action='show') - - mapper.redirect("", "/") - - if init_only is None or 'volumes' in init_only: - self.resources['volumes'] = volumes.create_resource(ext_mgr) - mapper.resource("volume", "volumes", - controller=self.resources['volumes'], - collection={'detail': 'GET'}, - member={'action': 'POST'}) - - if init_only is None or 'types' in init_only: - self.resources['types'] = types.create_resource() - mapper.resource("type", "types", - controller=self.resources['types']) - - if init_only is None or 'snapshots' in init_only: - self.resources['snapshots'] = snapshots.create_resource(ext_mgr) - mapper.resource("snapshot", "snapshots", - controller=self.resources['snapshots'], - collection={'detail': 'GET'}, - member={'action': 'POST'}) diff --git a/nova/api/openstack/volume/contrib/__init__.py b/nova/api/openstack/volume/contrib/__init__.py deleted file mode 100644 index 8e01d88d0..000000000 --- a/nova/api/openstack/volume/contrib/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Justin Santa Barbara -# 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. - -"""Contrib contains extensions that are shipped with nova. - -It can't be called 'extensions' because that causes namespacing problems. - -""" - -from nova.api.openstack import extensions -from nova import flags -from nova.openstack.common import log as logging - - -FLAGS = flags.FLAGS -LOG = logging.getLogger(__name__) - - -def standard_extensions(ext_mgr): - extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__) - - -def select_extensions(ext_mgr): - extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__, - FLAGS.osapi_volume_ext_list) diff --git a/nova/api/openstack/volume/contrib/admin_actions.py b/nova/api/openstack/volume/contrib/admin_actions.py deleted file mode 100644 index 7e93283f7..000000000 --- a/nova/api/openstack/volume/contrib/admin_actions.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright 2012 OpenStack, LLC. -# -# 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 webob -from webob import exc - -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova import db -from nova import exception -from nova.openstack.common import log as logging -from nova import volume - - -LOG = logging.getLogger(__name__) - - -class AdminController(wsgi.Controller): - """Abstract base class for AdminControllers.""" - - collection = None # api collection to extend - - # FIXME(clayg): this will be hard to keep up-to-date - # Concrete classes can expand or over-ride - valid_status = set([ - 'creating', - 'available', - 'deleting', - 'error', - 'error_deleting', - ]) - - def __init__(self, *args, **kwargs): - super(AdminController, self).__init__(*args, **kwargs) - # singular name of the resource - self.resource_name = self.collection.rstrip('s') - self.volume_api = volume.API() - - def _update(self, *args, **kwargs): - raise NotImplementedError() - - def _validate_status(self, status): - if status not in self.valid_status: - raise exc.HTTPBadRequest("Must specify a valid status") - - def authorize(self, context, action_name): - # e.g. "snapshot_admin_actions:reset_status" - action = '%s_admin_actions:%s' % (self.resource_name, action_name) - extensions.extension_authorizer('volume', action)(context) - - @wsgi.action('os-reset_status') - def _reset_status(self, req, id, body): - """Reset status on the resource.""" - context = req.environ['nova.context'] - self.authorize(context, 'reset_status') - try: - new_status = body['os-reset_status']['status'] - except (TypeError, KeyError): - raise exc.HTTPBadRequest("Must specify 'status'") - self._validate_status(new_status) - msg = _("Updating status of %(resource)s '%(id)s' to '%(status)s'") - LOG.debug(msg, {'resource': self.resource_name, 'id': id, - 'status': new_status}) - try: - self._update(context, id, {'status': new_status}) - except exception.NotFound, e: - raise exc.HTTPNotFound(e) - return webob.Response(status_int=202) - - -class VolumeAdminController(AdminController): - """AdminController for Volumes.""" - - collection = 'volumes' - valid_status = AdminController.valid_status.union( - set(['attaching', 'in-use', 'detaching'])) - - def _update(self, *args, **kwargs): - db.volume_update(*args, **kwargs) - - @wsgi.action('os-force_delete') - def _force_delete(self, req, id, body): - """Delete a resource, bypassing the check that it must be available.""" - context = req.environ['nova.context'] - self.authorize(context, 'force_delete') - try: - volume = self.volume_api.get(context, id) - except exception.NotFound: - raise exc.HTTPNotFound() - self.volume_api.delete(context, volume, force=True) - return webob.Response(status_int=202) - - -class SnapshotAdminController(AdminController): - """AdminController for Snapshots.""" - - collection = 'snapshots' - - def _update(self, *args, **kwargs): - db.snapshot_update(*args, **kwargs) - - -class Admin_actions(extensions.ExtensionDescriptor): - """Enable admin actions.""" - - name = "AdminActions" - alias = "os-admin-actions" - namespace = "http://docs.openstack.org/volume/ext/admin-actions/api/v1.1" - updated = "2012-08-25T00:00:00+00:00" - - def get_controller_extensions(self): - exts = [] - for class_ in (VolumeAdminController, SnapshotAdminController): - controller = class_() - extension = extensions.ControllerExtension( - self, class_.collection, controller) - exts.append(extension) - return exts diff --git a/nova/api/openstack/volume/contrib/image_create.py b/nova/api/openstack/volume/contrib/image_create.py deleted file mode 100644 index 840689799..000000000 --- a/nova/api/openstack/volume/contrib/image_create.py +++ /dev/null @@ -1,31 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright (c) 2012 NTT.
-# Copyright (c) 2012 OpenStack, LLC.
-# 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.
-
-"""The Create Volume from Image extension."""
-
-
-from nova.api.openstack import extensions
-
-
-class Image_create(extensions.ExtensionDescriptor):
- """Allow creating a volume from an image in the Create Volume v1 API"""
-
- name = "CreateVolumeExtension"
- alias = "os-image-create"
- namespace = "http://docs.openstack.org/volume/ext/image-create/api/v1"
- updated = "2012-08-13T00:00:00+00:00"
diff --git a/nova/api/openstack/volume/contrib/types_extra_specs.py b/nova/api/openstack/volume/contrib/types_extra_specs.py deleted file mode 100644 index 2e993ad8a..000000000 --- a/nova/api/openstack/volume/contrib/types_extra_specs.py +++ /dev/null @@ -1,149 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 OpenStack LLC. -# -# 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. - -"""The volume types extra specs extension""" - -import webob - -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import db -from nova import exception -from nova.volume import volume_types - - -authorize = extensions.extension_authorizer('volume', 'types_extra_specs') - - -class VolumeTypeExtraSpecsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecTemplate(xmlutil.TemplateBuilder): - def construct(self): - tagname = xmlutil.Selector('key') - - def extraspec_sel(obj, do_raise=False): - # Have to extract the key and value for later use... - key, value = obj.items()[0] - return dict(key=key, value=value) - - root = xmlutil.TemplateElement(tagname, selector=extraspec_sel) - root.text = 'value' - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecsController(wsgi.Controller): - """ The volume type extra specs API controller for the OpenStack API """ - - def _get_extra_specs(self, context, type_id): - extra_specs = db.volume_type_extra_specs_get(context, type_id) - specs_dict = {} - for key, value in extra_specs.iteritems(): - specs_dict[key] = value - return dict(extra_specs=specs_dict) - - def _check_type(self, context, type_id): - try: - volume_types.get_volume_type(context, type_id) - except exception.NotFound as ex: - raise webob.exc.HTTPNotFound(explanation=unicode(ex)) - - @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) - def index(self, req, type_id): - """ Returns the list of extra specs for a given volume type """ - context = req.environ['nova.context'] - authorize(context) - self._check_type(context, type_id) - return self._get_extra_specs(context, type_id) - - @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) - def create(self, req, type_id, body=None): - context = req.environ['nova.context'] - authorize(context) - - if not self.is_valid_body(body, 'extra_specs'): - raise webob.exc.HTTPUnprocessableEntity() - - self._check_type(context, type_id) - - specs = body['extra_specs'] - db.volume_type_extra_specs_update_or_create(context, - type_id, - specs) - return body - - @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) - def update(self, req, type_id, id, body=None): - context = req.environ['nova.context'] - authorize(context) - if not body: - raise webob.exc.HTTPUnprocessableEntity() - self._check_type(context, type_id) - if not id in body: - expl = _('Request body and URI mismatch') - raise webob.exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: - expl = _('Request body contains too many items') - raise webob.exc.HTTPBadRequest(explanation=expl) - db.volume_type_extra_specs_update_or_create(context, - type_id, - body) - return body - - @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) - def show(self, req, type_id, id): - """Return a single extra spec item.""" - context = req.environ['nova.context'] - authorize(context) - self._check_type(context, type_id) - specs = self._get_extra_specs(context, type_id) - if id in specs['extra_specs']: - return {id: specs['extra_specs'][id]} - else: - raise webob.exc.HTTPNotFound() - - def delete(self, req, type_id, id): - """ Deletes an existing extra spec """ - context = req.environ['nova.context'] - self._check_type(context, type_id) - authorize(context) - db.volume_type_extra_specs_delete(context, type_id, id) - return webob.Response(status_int=202) - - -class Types_extra_specs(extensions.ExtensionDescriptor): - """Types extra specs support""" - - name = "TypesExtraSpecs" - alias = "os-types-extra-specs" - namespace = "http://docs.openstack.org/volume/ext/types-extra-specs/api/v1" - updated = "2011-08-24T00:00:00+00:00" - - def get_resources(self): - resources = [] - res = extensions.ResourceExtension('extra_specs', - VolumeTypeExtraSpecsController(), - parent=dict( - member_name='type', - collection_name='types')) - resources.append(res) - - return resources diff --git a/nova/api/openstack/volume/contrib/types_manage.py b/nova/api/openstack/volume/contrib/types_manage.py deleted file mode 100644 index e68093ce8..000000000 --- a/nova/api/openstack/volume/contrib/types_manage.py +++ /dev/null @@ -1,91 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 OpenStack LLC. -# -# 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. - -"""The volume types manage extension.""" - -import webob - -from nova.api.openstack import extensions -from nova.api.openstack.volume import types -from nova.api.openstack.volume.views import types as views_types -from nova.api.openstack import wsgi -from nova import exception -from nova.volume import volume_types - - -authorize = extensions.extension_authorizer('volume', 'types_manage') - - -class VolumeTypesManageController(wsgi.Controller): - """ The volume types API controller for the OpenStack API """ - - _view_builder_class = views_types.ViewBuilder - - @wsgi.action("create") - @wsgi.serializers(xml=types.VolumeTypeTemplate) - def _create(self, req, body): - """Creates a new volume type.""" - context = req.environ['nova.context'] - authorize(context) - - if not self.is_valid_body(body, 'volume_type'): - raise webob.exc.HTTPUnprocessableEntity() - - vol_type = body['volume_type'] - name = vol_type.get('name', None) - specs = vol_type.get('extra_specs', {}) - - if name is None or name == "": - raise webob.exc.HTTPUnprocessableEntity() - - try: - volume_types.create(context, name, specs) - vol_type = volume_types.get_volume_type_by_name(context, name) - except exception.VolumeTypeExists as err: - raise webob.exc.HTTPConflict(explanation=str(err)) - except exception.NotFound: - raise webob.exc.HTTPNotFound() - - return self._view_builder.show(req, vol_type) - - @wsgi.action("delete") - def _delete(self, req, id): - """ Deletes an existing volume type """ - context = req.environ['nova.context'] - authorize(context) - - try: - vol_type = volume_types.get_volume_type(context, id) - volume_types.destroy(context, vol_type['name']) - except exception.NotFound: - raise webob.exc.HTTPNotFound() - - return webob.Response(status_int=202) - - -class Types_manage(extensions.ExtensionDescriptor): - """Types manage support""" - - name = "TypesManage" - alias = "os-types-manage" - namespace = "http://docs.openstack.org/volume/ext/types-manage/api/v1" - updated = "2011-08-24T00:00:00+00:00" - - def get_controller_extensions(self): - controller = VolumeTypesManageController() - extension = extensions.ControllerExtension(self, 'types', controller) - return [extension] diff --git a/nova/api/openstack/volume/contrib/volume_actions.py b/nova/api/openstack/volume/contrib/volume_actions.py deleted file mode 100644 index 8a453bfb1..000000000 --- a/nova/api/openstack/volume/contrib/volume_actions.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2012 OpenStack, LLC. -# -# 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 webob -from xml.dom import minidom - -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import exception -from nova import flags -from nova.openstack.common import log as logging -from nova.openstack.common.rpc import common as rpc_common -from nova import volume - - -FLAGS = flags.FLAGS -LOG = logging.getLogger(__name__) - - -def authorize(context, action_name): - action = 'volume_actions:%s' % action_name - extensions.extension_authorizer('volume', action)(context) - - -class VolumeToImageSerializer(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('os-volume_upload_image', - selector='os-volume_upload_image') - root.set('id') - root.set('updated_at') - root.set('status') - root.set('display_description') - root.set('size') - root.set('volume_type') - root.set('image_id') - root.set('container_format') - root.set('disk_format') - root.set('image_name') - return xmlutil.MasterTemplate(root, 1) - - -class VolumeToImageDeserializer(wsgi.XMLDeserializer): - """Deserializer to handle xml-formatted requests""" - def default(self, string): - dom = minidom.parseString(string) - action_node = dom.childNodes[0] - action_name = action_node.tagName - - action_data = {} - attributes = ["force", "image_name", "container_format", "disk_format"] - for attr in attributes: - if action_node.hasAttribute(attr): - action_data[attr] = action_node.getAttribute(attr) - if 'force' in action_data and action_data['force'] == 'True': - action_data['force'] = True - return {'body': {action_name: action_data}} - - -class VolumeActionsController(wsgi.Controller): - def __init__(self, *args, **kwargs): - super(VolumeActionsController, self).__init__(*args, **kwargs) - self.volume_api = volume.API() - - @wsgi.response(202) - @wsgi.action('os-volume_upload_image') - @wsgi.serializers(xml=VolumeToImageSerializer) - @wsgi.deserializers(xml=VolumeToImageDeserializer) - def _volume_upload_image(self, req, id, body): - """Uploads the specified volume to image service.""" - context = req.environ['nova.context'] - try: - params = body['os-volume_upload_image'] - except (TypeError, KeyError): - msg = _("Invalid request body") - raise webob.exc.HTTPBadRequest(explanation=msg) - - if not params.get("image_name"): - msg = _("No image_name was specified in request.") - raise webob.exc.HTTPBadRequest(explanation=msg) - - force = params.get('force', False) - try: - volume = self.volume_api.get(context, id) - except exception.VolumeNotFound, error: - raise webob.exc.HTTPNotFound(explanation=unicode(error)) - authorize(context, "upload_image") - image_metadata = {"container_format": params.get("container_format", - "bare"), - "disk_format": params.get("disk_format", "raw"), - "name": params["image_name"]} - try: - response = self.volume_api.copy_volume_to_image(context, - volume, - image_metadata, - force) - except exception.InvalidVolume, error: - raise webob.exc.HTTPBadRequest(explanation=unicode(error)) - except ValueError, error: - raise webob.exc.HTTPBadRequest(explanation=unicode(error)) - except rpc_common.RemoteError as error: - msg = "%(err_type)s: %(err_msg)s" % {'err_type': error.exc_type, - 'err_msg': error.value} - raise webob.exc.HTTPBadRequest(explanation=msg) - return {'os-volume_upload_image': response} - - -class Volume_actions(extensions.ExtensionDescriptor): - """Enable volume actions - """ - - name = "VolumeActions" - alias = "os-volume-actions" - namespace = "http://docs.openstack.org/volume/ext/volume-actions/api/v1.1" - updated = "2012-05-31T00:00:00+00:00" - - def get_controller_extensions(self): - controller = VolumeActionsController() - extension = extensions.ControllerExtension(self, 'volumes', controller) - return [extension] diff --git a/nova/api/openstack/volume/extensions.py b/nova/api/openstack/volume/extensions.py deleted file mode 100644 index b21b9d145..000000000 --- a/nova/api/openstack/volume/extensions.py +++ /dev/null @@ -1,34 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# 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 nova.api.openstack import extensions as base_extensions -from nova import flags -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) -FLAGS = flags.FLAGS - - -class ExtensionManager(base_extensions.ExtensionManager): - def __init__(self): - LOG.audit(_('Initializing extension manager.')) - self.cls_list = FLAGS.osapi_volume_extension - self.extensions = {} - self.plugins = [] - self.sorted_ext_list = [] - self._load_extensions() diff --git a/nova/api/openstack/volume/snapshots.py b/nova/api/openstack/volume/snapshots.py deleted file mode 100644 index 74c5f75e6..000000000 --- a/nova/api/openstack/volume/snapshots.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# 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. - -"""The volumes snapshots api.""" - -import webob -from webob import exc - -from nova.api.openstack import common -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import exception -from nova import flags -from nova.openstack.common import log as logging -from nova import utils -from nova import volume - - -LOG = logging.getLogger(__name__) - - -FLAGS = flags.FLAGS - - -def _translate_snapshot_detail_view(context, vol): - """Maps keys for snapshots details view.""" - - d = _translate_snapshot_summary_view(context, vol) - - # NOTE(gagupta): No additional data / lookups at the moment - return d - - -def _translate_snapshot_summary_view(context, vol): - """Maps keys for snapshots summary view.""" - d = {} - - # TODO(bcwaldon): remove str cast once we use uuids - d['id'] = str(vol['id']) - d['volume_id'] = str(vol['volume_id']) - d['status'] = vol['status'] - # NOTE(gagupta): We map volume_size as the snapshot size - d['size'] = vol['volume_size'] - d['created_at'] = vol['created_at'] - d['display_name'] = vol['display_name'] - d['display_description'] = vol['display_description'] - return d - - -def make_snapshot(elem): - elem.set('id') - elem.set('status') - elem.set('size') - elem.set('created_at') - elem.set('display_name') - elem.set('display_description') - elem.set('volume_id') - - -class SnapshotTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('snapshot', selector='snapshot') - make_snapshot(root) - return xmlutil.MasterTemplate(root, 1) - - -class SnapshotsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('snapshots') - elem = xmlutil.SubTemplateElement(root, 'snapshot', - selector='snapshots') - make_snapshot(elem) - return xmlutil.MasterTemplate(root, 1) - - -class SnapshotsController(wsgi.Controller): - """The Volumes API controller for the OpenStack API.""" - - def __init__(self, ext_mgr=None): - self.volume_api = volume.API() - self.ext_mgr = ext_mgr - super(SnapshotsController, self).__init__() - - @wsgi.serializers(xml=SnapshotTemplate) - def show(self, req, id): - """Return data about the given snapshot.""" - context = req.environ['nova.context'] - - try: - vol = self.volume_api.get_snapshot(context, id) - except exception.NotFound: - raise exc.HTTPNotFound() - - return {'snapshot': _translate_snapshot_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a snapshot.""" - context = req.environ['nova.context'] - - LOG.audit(_("Delete snapshot with id: %s"), id, context=context) - - try: - snapshot = self.volume_api.get_snapshot(context, id) - self.volume_api.delete_snapshot(context, snapshot) - except exception.NotFound: - raise exc.HTTPNotFound() - return webob.Response(status_int=202) - - @wsgi.serializers(xml=SnapshotsTemplate) - def index(self, req): - """Returns a summary list of snapshots.""" - return self._items(req, entity_maker=_translate_snapshot_summary_view) - - @wsgi.serializers(xml=SnapshotsTemplate) - def detail(self, req): - """Returns a detailed list of snapshots.""" - return self._items(req, entity_maker=_translate_snapshot_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of snapshots, transformed through entity_maker.""" - context = req.environ['nova.context'] - - search_opts = {} - search_opts.update(req.GET) - - snapshots = self.volume_api.get_all_snapshots(context, - search_opts=search_opts) - limited_list = common.limited(snapshots, req) - res = [entity_maker(context, snapshot) for snapshot in limited_list] - return {'snapshots': res} - - @wsgi.serializers(xml=SnapshotTemplate) - def create(self, req, body): - """Creates a new snapshot.""" - context = req.environ['nova.context'] - - if not self.is_valid_body(body, 'snapshot'): - raise exc.HTTPUnprocessableEntity() - - snapshot = body['snapshot'] - volume_id = snapshot['volume_id'] - - try: - volume = self.volume_api.get(context, volume_id) - except exception.VolumeNotFound as err: - raise exc.HTTPNotFound(explanation=unicode(err)) - - force = snapshot.get('force', False) - msg = _("Create snapshot from volume %s") - LOG.audit(msg, volume_id, context=context) - - if not utils.is_valid_boolstr(force): - msg = _("Invalid value '%s' for force. ") % force - raise exception.InvalidParameterValue(err=msg) - - if utils.bool_from_str(force): - new_snapshot = self.volume_api.create_snapshot_force(context, - volume, - snapshot.get('display_name'), - snapshot.get('display_description')) - else: - new_snapshot = self.volume_api.create_snapshot(context, - volume, - snapshot.get('display_name'), - snapshot.get('display_description')) - - retval = _translate_snapshot_detail_view(context, new_snapshot) - - return {'snapshot': retval} - - -def create_resource(ext_mgr): - return wsgi.Resource(SnapshotsController(ext_mgr)) diff --git a/nova/api/openstack/volume/types.py b/nova/api/openstack/volume/types.py deleted file mode 100644 index a39d2a1f6..000000000 --- a/nova/api/openstack/volume/types.py +++ /dev/null @@ -1,80 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 OpenStack LLC. -# -# 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. - -""" The volume type & volume types extra specs extension""" - -from webob import exc - -from nova.api.openstack.volume.views import types as views_types -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import exception -from nova.volume import volume_types - - -def make_voltype(elem): - elem.set('id') - elem.set('name') - extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - elem.append(extra_specs) - - -class VolumeTypeTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume_type', selector='volume_type') - make_voltype(root) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume_types') - elem = xmlutil.SubTemplateElement(root, 'volume_type', - selector='volume_types') - make_voltype(elem) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypesController(wsgi.Controller): - """ The volume types API controller for the OpenStack API """ - - _view_builder_class = views_types.ViewBuilder - - @wsgi.serializers(xml=VolumeTypesTemplate) - def index(self, req): - """ Returns the list of volume types """ - context = req.environ['nova.context'] - vol_types = volume_types.get_all_types(context).values() - return self._view_builder.index(req, vol_types) - - @wsgi.serializers(xml=VolumeTypeTemplate) - def show(self, req, id): - """ Return a single volume type item """ - context = req.environ['nova.context'] - - try: - vol_type = volume_types.get_volume_type(context, id) - except exception.NotFound: - raise exc.HTTPNotFound() - - # TODO(bcwaldon): remove str cast once we use uuids - vol_type['id'] = str(vol_type['id']) - return self._view_builder.show(req, vol_type) - - -def create_resource(): - return wsgi.Resource(VolumeTypesController()) diff --git a/nova/api/openstack/volume/versions.py b/nova/api/openstack/volume/versions.py deleted file mode 100644 index 68d34b1f9..000000000 --- a/nova/api/openstack/volume/versions.py +++ /dev/null @@ -1,83 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# 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 nova.api.openstack.compute import versions -from nova.api.openstack.volume.views import versions as views_versions -from nova.api.openstack import wsgi - - -VERSIONS = { - "v1.0": { - "id": "v1.0", - "status": "CURRENT", - "updated": "2012-01-04T11:33:21Z", - "links": [ - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://jorgew.github.com/block-storage-api/" - "content/os-block-storage-1.0.pdf", - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - #(anthony) FIXME - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl", - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.volume+xml;version=1", - }, - { - "base": "application/json", - "type": "application/vnd.openstack.volume+json;version=1", - } - ], - } -} - - -class Versions(versions.Versions): - @wsgi.serializers(xml=versions.VersionsTemplate, - atom=versions.VersionsAtomSerializer) - def index(self, req): - """Return all versions.""" - builder = views_versions.get_view_builder(req) - return builder.build_versions(VERSIONS) - - @wsgi.serializers(xml=versions.ChoicesTemplate) - @wsgi.response(300) - def multi(self, req): - """Return multiple choices.""" - builder = views_versions.get_view_builder(req) - return builder.build_choices(VERSIONS, req) - - -class VolumeVersionV1(object): - @wsgi.serializers(xml=versions.VersionTemplate, - atom=versions.VersionAtomSerializer) - def show(self, req): - builder = views_versions.get_view_builder(req) - return builder.build_version(VERSIONS['v1.0']) - - -def create_resource(): - return wsgi.Resource(VolumeVersionV1()) diff --git a/nova/api/openstack/volume/views/__init__.py b/nova/api/openstack/volume/views/__init__.py deleted file mode 100644 index d65c689a8..000000000 --- a/nova/api/openstack/volume/views/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# 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. diff --git a/nova/api/openstack/volume/views/types.py b/nova/api/openstack/volume/views/types.py deleted file mode 100644 index 8274a0c6f..000000000 --- a/nova/api/openstack/volume/views/types.py +++ /dev/null @@ -1,34 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Red Hat, Inc. -# 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 nova.api.openstack import common - - -class ViewBuilder(common.ViewBuilder): - - def show(self, request, volume_type, brief=False): - """Trim away extraneous volume type attributes.""" - trimmed = dict(id=volume_type.get('id'), - name=volume_type.get('name'), - extra_specs=volume_type.get('extra_specs')) - return trimmed if brief else dict(volume_type=trimmed) - - def index(self, request, volume_types): - """Index over trimmed volume types""" - volume_types_list = [self.show(request, volume_type, True) - for volume_type in volume_types] - return dict(volume_types=volume_types_list) diff --git a/nova/api/openstack/volume/views/versions.py b/nova/api/openstack/volume/views/versions.py deleted file mode 100644 index 2e659af6a..000000000 --- a/nova/api/openstack/volume/views/versions.py +++ /dev/null @@ -1,36 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010-2011 OpenStack LLC. -# 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 os - -from nova.api.openstack.compute.views import versions as compute_views - - -def get_view_builder(req): - base_url = req.application_url - return ViewBuilder(base_url) - - -class ViewBuilder(compute_views.ViewBuilder): - def generate_href(self, path=None): - """Create an url that refers to a specific version_number.""" - version_number = 'v1' - if path: - path = path.strip('/') - return os.path.join(self.base_url, version_number, path) - else: - return os.path.join(self.base_url, version_number) + '/' diff --git a/nova/api/openstack/volume/volumes.py b/nova/api/openstack/volume/volumes.py deleted file mode 100644 index e13f04036..000000000 --- a/nova/api/openstack/volume/volumes.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# 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. - -"""The volumes api.""" - -import webob -from webob import exc -from xml.dom import minidom - -from nova.api.openstack import common -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova import exception -from nova import flags -from nova.openstack.common import log as logging -from nova import utils -from nova import volume -from nova.volume import volume_types - - -LOG = logging.getLogger(__name__) - - -FLAGS = flags.FLAGS - - -def _translate_attachment_detail_view(_context, vol): - """Maps keys for attachment details view.""" - - d = _translate_attachment_summary_view(_context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_attachment_summary_view(_context, vol): - """Maps keys for attachment summary view.""" - d = {} - - volume_id = vol['id'] - - # NOTE(justinsb): We use the volume id as the id of the attachment object - d['id'] = volume_id - - d['volume_id'] = volume_id - d['server_id'] = vol['instance_uuid'] - if vol.get('mountpoint'): - d['device'] = vol['mountpoint'] - - return d - - -def _translate_volume_detail_view(context, vol, image_id=None): - """Maps keys for volumes details view.""" - - d = _translate_volume_summary_view(context, vol, image_id) - - # No additional data / lookups at the moment - - return d - - -def _translate_volume_summary_view(context, vol, image_id=None): - """Maps keys for volumes summary view.""" - d = {} - - d['id'] = vol['id'] - d['status'] = vol['status'] - d['size'] = vol['size'] - d['availability_zone'] = vol['availability_zone'] - d['created_at'] = vol['created_at'] - - d['attachments'] = [] - if vol['attach_status'] == 'attached': - attachment = _translate_attachment_detail_view(context, vol) - d['attachments'].append(attachment) - - d['display_name'] = vol['display_name'] - d['display_description'] = vol['display_description'] - - if vol['volume_type_id'] and vol.get('volume_type'): - d['volume_type'] = vol['volume_type']['name'] - else: - # TODO(bcwaldon): remove str cast once we use uuids - d['volume_type'] = str(vol['volume_type_id']) - - d['snapshot_id'] = vol['snapshot_id'] - - if image_id: - d['image_id'] = image_id - - LOG.audit(_("vol=%s"), vol, context=context) - - if vol.get('volume_metadata'): - metadata = vol.get('volume_metadata') - d['metadata'] = dict((item['key'], item['value']) for item in metadata) - else: - d['metadata'] = {} - - return d - - -def make_attachment(elem): - elem.set('id') - elem.set('server_id') - elem.set('volume_id') - elem.set('device') - - -def make_volume(elem): - elem.set('id') - elem.set('status') - elem.set('size') - elem.set('availability_zone') - elem.set('created_at') - elem.set('display_name') - elem.set('display_description') - elem.set('volume_type') - elem.set('snapshot_id') - - attachments = xmlutil.SubTemplateElement(elem, 'attachments') - attachment = xmlutil.SubTemplateElement(attachments, 'attachment', - selector='attachments') - make_attachment(attachment) - - # Attach metadata node - elem.append(common.MetadataTemplate()) - - -class VolumeTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume', selector='volume') - make_volume(root) - return xmlutil.MasterTemplate(root, 1) - - -class VolumesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volumes') - elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes') - make_volume(elem) - return xmlutil.MasterTemplate(root, 1) - - -class CommonDeserializer(wsgi.MetadataXMLDeserializer): - """Common deserializer to handle xml-formatted volume requests. - - Handles standard volume attributes as well as the optional metadata - attribute - """ - - metadata_deserializer = common.MetadataXMLDeserializer() - - def _extract_volume(self, node): - """Marshal the volume attribute of a parsed request.""" - volume = {} - volume_node = self.find_first_child_named(node, 'volume') - - attributes = ['display_name', 'display_description', 'size', - 'volume_type', 'availability_zone'] - for attr in attributes: - if volume_node.getAttribute(attr): - volume[attr] = volume_node.getAttribute(attr) - - metadata_node = self.find_first_child_named(volume_node, 'metadata') - if metadata_node is not None: - volume['metadata'] = self.extract_metadata(metadata_node) - - return volume - - -class CreateDeserializer(CommonDeserializer): - """Deserializer to handle xml-formatted create volume requests. - - Handles standard volume attributes as well as the optional metadata - attribute - """ - - def default(self, string): - """Deserialize an xml-formatted volume create request.""" - dom = minidom.parseString(string) - volume = self._extract_volume(dom) - return {'body': {'volume': volume}} - - -class VolumeController(wsgi.Controller): - """The Volumes API controller for the OpenStack API.""" - - def __init__(self, ext_mgr): - self.volume_api = volume.API() - self.ext_mgr = ext_mgr - super(VolumeController, self).__init__() - - @wsgi.serializers(xml=VolumeTemplate) - def show(self, req, id): - """Return data about the given volume.""" - context = req.environ['nova.context'] - - try: - vol = self.volume_api.get(context, id) - except exception.NotFound: - raise exc.HTTPNotFound() - - return {'volume': _translate_volume_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a volume.""" - context = req.environ['nova.context'] - - LOG.audit(_("Delete volume with id: %s"), id, context=context) - - try: - volume = self.volume_api.get(context, id) - self.volume_api.delete(context, volume) - except exception.NotFound: - raise exc.HTTPNotFound() - return webob.Response(status_int=202) - - @wsgi.serializers(xml=VolumesTemplate) - def index(self, req): - """Returns a summary list of volumes.""" - return self._items(req, entity_maker=_translate_volume_summary_view) - - @wsgi.serializers(xml=VolumesTemplate) - def detail(self, req): - """Returns a detailed list of volumes.""" - return self._items(req, entity_maker=_translate_volume_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker.""" - - search_opts = {} - search_opts.update(req.GET) - - context = req.environ['nova.context'] - remove_invalid_options(context, - search_opts, self._get_volume_search_options()) - - volumes = self.volume_api.get_all(context, search_opts=search_opts) - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumes': res} - - def _image_uuid_from_href(self, image_href): - # If the image href was generated by nova api, strip image_href - # down to an id. - try: - image_uuid = image_href.split('/').pop() - except (TypeError, AttributeError): - msg = _("Invalid imageRef provided.") - raise exc.HTTPBadRequest(explanation=msg) - - if not utils.is_uuid_like(image_uuid): - msg = _("Invalid imageRef provided.") - raise exc.HTTPBadRequest(explanation=msg) - - return image_uuid - - @wsgi.serializers(xml=VolumeTemplate) - @wsgi.deserializers(xml=CreateDeserializer) - def create(self, req, body): - """Creates a new volume.""" - if not self.is_valid_body(body, 'volume'): - msg = _("Invalid request body. 'volume' not found") - raise exc.HTTPUnprocessableEntity(explanation=msg) - - context = req.environ['nova.context'] - volume = body['volume'] - - kwargs = {} - - req_volume_type = volume.get('volume_type', None) - if req_volume_type: - try: - kwargs['volume_type'] = volume_types.get_volume_type_by_name( - context, req_volume_type) - except exception.NotFound: - raise exc.HTTPNotFound() - - kwargs['metadata'] = volume.get('metadata', None) - - snapshot_id = volume.get('snapshot_id') - if snapshot_id is not None: - kwargs['snapshot'] = self.volume_api.get_snapshot(context, - snapshot_id) - else: - kwargs['snapshot'] = None - - size = volume.get('size', None) - if size is None and kwargs['snapshot'] is not None: - size = kwargs['snapshot']['volume_size'] - - if size is None: - msg = _("Invalid request body. 'size' not found") - raise exc.HTTPUnprocessableEntity(explanation=msg) - - LOG.audit(_("Create volume of %s GB"), size, context=context) - - image_href = None - image_uuid = None - if self.ext_mgr.is_loaded('os-image-create'): - image_href = volume.get('imageRef') - if snapshot_id and image_href: - msg = _("Snapshot and image cannot be specified together.") - raise exc.HTTPBadRequest(explanation=msg) - if image_href: - image_uuid = self._image_uuid_from_href(image_href) - kwargs['image_id'] = image_uuid - - kwargs['availability_zone'] = volume.get('availability_zone', None) - - new_volume = self.volume_api.create(context, - size, - volume.get('display_name'), - volume.get('display_description'), - **kwargs) - - # TODO(vish): Instance should be None at db layer instead of - # trying to lazy load, but for now we turn it into - # a dict to avoid an error. - retval = _translate_volume_detail_view(context, dict(new_volume), - image_uuid) - - result = {'volume': retval} - - location = '%s/%s' % (req.url, new_volume['id']) - - return wsgi.ResponseObject(result, headers=dict(location=location)) - - def _get_volume_search_options(self): - """Return volume search options allowed by non-admin.""" - return ('name', 'status') - - -def create_resource(ext_mgr): - return wsgi.Resource(VolumeController(ext_mgr)) - - -def remove_invalid_options(context, search_options, allowed_search_options): - """Remove search options that are not valid for non-admin API/context.""" - if context.is_admin: - # Allow all options - return - # Otherwise, strip out all unknown options - unknown_options = [opt for opt in search_options - if opt not in allowed_search_options] - bad_options = ", ".join(unknown_options) - log_msg = _("Removing options '%(bad_options)s' from query") % locals() - LOG.debug(log_msg) - for opt in unknown_options: - search_options.pop(opt, None) |
