summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/extensions.py116
-rw-r--r--nova/api/openstack/incubator/__init__.py4
-rw-r--r--nova/api/openstack/incubator/volumes.py355
-rw-r--r--nova/api/openstack/incubator/volumes/__init__.py18
-rw-r--r--nova/api/openstack/incubator/volumes/volume_attachments.py184
-rw-r--r--nova/api/openstack/incubator/volumes/volumes.py161
-rw-r--r--nova/api/openstack/incubator/volumes/volumes_ext.py55
7 files changed, 407 insertions, 486 deletions
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index d0b4d378a..d1d479313 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -38,7 +38,10 @@ FLAGS = flags.FLAGS
class ExtensionDescriptor(object):
- """This is the base class that defines the contract for extensions."""
+ """Base class that defines the contract for extensions.
+
+ Note that you don't have to derive from this class to have a valid
+ extension; it is purely a convenience."""
def get_name(self):
"""The name of the extension.
@@ -321,22 +324,37 @@ class ExtensionManager(object):
resources = []
resources.append(ResourceExtension('extensions',
ExtensionController(self)))
- for _alias, ext in self.extensions.iteritems():
- resources.extend(ext.get_resources())
+ for alias, ext in self.extensions.iteritems():
+ try:
+ resources.extend(ext.get_resources())
+ except AttributeError:
+ # NOTE(dprince): Extension aren't required to have resource
+ # extensions
+ pass
return resources
def get_actions(self):
"""Returns a list of ActionExtension objects."""
actions = []
- for _alias, ext in self.extensions.iteritems():
- actions.extend(ext.get_actions())
+ for alias, ext in self.extensions.iteritems():
+ try:
+ actions.extend(ext.get_actions())
+ except AttributeError:
+ # NOTE(dprince): Extension aren't required to have action
+ # extensions
+ pass
return actions
def get_response_extensions(self):
"""Returns a list of ResponseExtension objects."""
response_exts = []
- for _alias, ext in self.extensions.iteritems():
- response_exts.extend(ext.get_response_extensions())
+ for alias, ext in self.extensions.iteritems():
+ try:
+ response_exts.extend(ext.get_response_extensions())
+ except AttributeError:
+ # NOTE(dprince): Extension aren't required to have response
+ # extensions
+ pass
return response_exts
def _check_extension(self, extension):
@@ -353,78 +371,42 @@ class ExtensionManager(object):
def _load_all_extensions(self):
"""Load extensions from the configured path.
- An extension consists of a directory of related files, with a class
- that defines a class that inherits from ExtensionDescriptor.
+ Load extensions from the configured path. The extension name is
+ constructed from the module_name. If your extension module was named
+ widgets.py the extension class within that module should be
+ 'Widgets'.
- Because of some oddities involving identically named modules, it's
- probably best to name your file after the name of your extension,
- rather than something likely to clash like 'extension.py'.
-
- The name of your directory should be the same as the alias your
- extension uses, for everyone's sanity.
+ In addition, extensions are loaded from the 'incubator' directory.
See nova/tests/api/openstack/extensions/foxinsocks.py for an example
extension implementation.
"""
- self._load_extensions_under_path(self.path)
+ if os.path.exists(self.path):
+ self._load_all_extensions_from_path(self.path)
incubator_path = os.path.join(os.path.dirname(__file__), "incubator")
- self._load_extensions_under_path(incubator_path)
-
- def _load_extensions_under_path(self, path):
- if not os.path.isdir(path):
- LOG.warning(_('Extensions directory not found: %s') % path)
- return
-
- LOG.debug(_('Looking for extensions in: %s') % path)
-
- for child in os.listdir(path):
- child_path = os.path.join(path, child)
- if not os.path.isdir(child_path):
- continue
- self._load_extension(child_path)
-
- def _load_extension(self, path):
- if not os.path.isdir(path):
- return
+ if os.path.exists(incubator_path):
+ self._load_all_extensions_from_path(incubator_path)
+ def _load_all_extensions_from_path(self, path):
for f in os.listdir(path):
+ LOG.audit(_('Loading extension file: %s'), f)
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
- if mod_name.startswith('_'):
- continue
- if file_ext.lower() != '.py':
- continue
-
ext_path = os.path.join(path, f)
- if self.super_verbose:
- LOG.debug(_('Checking extension file: %s'), ext_path)
-
- mod = imp.load_source(mod_name, ext_path)
- for _name, cls in inspect.getmembers(mod):
- try:
- if not inspect.isclass(cls):
- continue
-
- # NOTE(justinsb): It seems that python modules are odd.
- # If you have two identically named modules, the classes
- # from both are mixed in. So name your extension based
- # on the alias, not 'extension.py'!
- # TODO(justinsb): Any way to work around this?
-
- if self.super_verbose:
- LOG.debug(_('Checking class: %s'), cls)
-
- if not ExtensionDescriptor in cls.__bases__:
- if self.super_verbose:
- LOG.debug(_('Not a ExtensionDescriptor: %s'), cls)
- continue
-
- obj = cls()
- self._add_extension(obj)
- except AttributeError as ex:
- LOG.exception(_("Exception loading extension: %s"),
- unicode(ex))
+ if file_ext.lower() == '.py' and not mod_name.startswith('_'):
+ mod = imp.load_source(mod_name, ext_path)
+ ext_name = mod_name[0].upper() + mod_name[1:]
+ new_ext_class = getattr(mod, ext_name, None)
+ if not new_ext_class:
+ LOG.warn(_('Did not find expected name '
+ '"%(ext_name)s" in %(file)s'),
+ {'ext_name': ext_name,
+ 'file': ext_path})
+ continue
+ new_ext = new_ext_class()
+ self._check_extension(new_ext)
+ self._add_extension(new_ext)
def _add_extension(self, ext):
alias = ext.get_alias()
diff --git a/nova/api/openstack/incubator/__init__.py b/nova/api/openstack/incubator/__init__.py
index cded38174..e115f1ab9 100644
--- a/nova/api/openstack/incubator/__init__.py
+++ b/nova/api/openstack/incubator/__init__.py
@@ -17,4 +17,6 @@
"""Incubator contains extensions that are shipped with nova.
-It can't be called 'extensions' because that causes namespacing problems."""
+It can't be called 'extensions' because that causes namespacing problems.
+
+"""
diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py
new file mode 100644
index 000000000..7a5b2c1d0
--- /dev/null
+++ b/nova/api/openstack/incubator/volumes.py
@@ -0,0 +1,355 @@
+# 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 extension."""
+
+from webob import exc
+
+from nova import compute
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import volume
+from nova import wsgi
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova.api.openstack import faults
+
+
+LOG = logging.getLogger("nova.api.volumes")
+
+
+FLAGS = flags.FLAGS
+
+
+def _translate_volume_detail_view(context, vol):
+ """Maps keys for volumes details view."""
+
+ d = _translate_volume_summary_view(context, vol)
+
+ # No additional data / lookups at the moment
+
+ return d
+
+
+def _translate_volume_summary_view(_context, vol):
+ """Maps keys for volumes summary view."""
+ d = {}
+
+ instance_id = None
+ # instance_data = None
+ attached_to = vol.get('instance')
+ if attached_to:
+ instance_id = attached_to['id']
+ # instance_data = '%s[%s]' % (instance_ec2_id,
+ # attached_to['host'])
+ d['id'] = vol['id']
+ d['status'] = vol['status']
+ d['size'] = vol['size']
+ d['availabilityZone'] = vol['availability_zone']
+ d['createdAt'] = vol['created_at']
+ # if context.is_admin:
+ # v['status'] = '%s (%s, %s, %s, %s)' % (
+ # vol['status'],
+ # vol['user_id'],
+ # vol['host'],
+ # instance_data,
+ # vol['mountpoint'])
+ if vol['attach_status'] == 'attached':
+ d['attachments'] = [{'attachTime': vol['attach_time'],
+ 'deleteOnTermination': False,
+ 'mountpoint': vol['mountpoint'],
+ 'instanceId': instance_id,
+ 'status': 'attached',
+ 'volumeId': vol['id']}]
+ else:
+ d['attachments'] = [{}]
+
+ d['displayName'] = vol['display_name']
+ d['displayDescription'] = vol['display_description']
+ return d
+
+
+class VolumeController(wsgi.Controller):
+ """The Volumes API controller for the OpenStack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "volume": [
+ "id",
+ "status",
+ "size",
+ "availabilityZone",
+ "createdAt",
+ "displayName",
+ "displayDescription",
+ ]}}}
+
+ def __init__(self):
+ self.volume_api = volume.API()
+ super(VolumeController, self).__init__()
+
+ 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:
+ return faults.Fault(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:
+ self.volume_api.delete(context, volume_id=id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+ def index(self, req):
+ """Returns a summary list of volumes."""
+ return self._items(req, entity_maker=_translate_volume_summary_view)
+
+ 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."""
+ context = req.environ['nova.context']
+
+ volumes = self.volume_api.get_all(context)
+ limited_list = common.limited(volumes, req)
+ res = [entity_maker(context, vol) for vol in limited_list]
+ return {'volumes': res}
+
+ def create(self, req):
+ """Creates a new volume."""
+ context = req.environ['nova.context']
+
+ env = self._deserialize(req.body, req.get_content_type())
+ if not env:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ vol = env['volume']
+ size = vol['size']
+ LOG.audit(_("Create volume of %s GB"), size, context=context)
+ new_volume = self.volume_api.create(context, size,
+ vol.get('display_name'),
+ vol.get('display_description'))
+
+ # Work around problem that instance is lazy-loaded...
+ new_volume['instance'] = None
+
+ retval = _translate_volume_detail_view(context, new_volume)
+
+ return {'volume': retval}
+
+
+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['volumeId'] = volume_id
+ if vol.get('instance_id'):
+ d['serverId'] = vol['instance_id']
+ if vol.get('mountpoint'):
+ d['device'] = vol['mountpoint']
+
+ return d
+
+
+class VolumeAttachmentController(wsgi.Controller):
+ """The volume attachment API controller for the Openstack API.
+
+ A child resource of the server. Note that we use the volume id
+ as the ID of the attachment (though this is not guaranteed externally)
+
+ """
+
+ _serialization_metadata = {
+ 'application/xml': {
+ 'attributes': {
+ 'volumeAttachment': ['id',
+ 'serverId',
+ 'volumeId',
+ 'device']}}}
+
+ def __init__(self):
+ self.compute_api = compute.API()
+ self.volume_api = volume.API()
+ super(VolumeAttachmentController, self).__init__()
+
+ def index(self, req, server_id):
+ """Returns the list of volume attachments for a given instance."""
+ return self._items(req, server_id,
+ entity_maker=_translate_attachment_summary_view)
+
+ def show(self, req, server_id, id):
+ """Return data about the given volume."""
+ context = req.environ['nova.context']
+
+ volume_id = id
+ try:
+ vol = self.volume_api.get(context, volume_id)
+ except exception.NotFound:
+ LOG.debug("volume_id not found")
+ return faults.Fault(exc.HTTPNotFound())
+
+ if str(vol['instance_id']) != server_id:
+ LOG.debug("instance_id != server_id")
+ return faults.Fault(exc.HTTPNotFound())
+
+ return {'volumeAttachment': _translate_attachment_detail_view(context,
+ vol)}
+
+ def create(self, req, server_id):
+ """Attach a volume to an instance."""
+ context = req.environ['nova.context']
+
+ env = self._deserialize(req.body, req.get_content_type())
+ if not env:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ instance_id = server_id
+ volume_id = env['volumeAttachment']['volumeId']
+ device = env['volumeAttachment']['device']
+
+ msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
+ " at %(device)s") % locals()
+ LOG.audit(msg, context=context)
+
+ try:
+ self.compute_api.attach_volume(context,
+ instance_id=instance_id,
+ volume_id=volume_id,
+ device=device)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ # The attach is async
+ attachment = {}
+ attachment['id'] = volume_id
+ attachment['volumeId'] = volume_id
+
+ # NOTE(justinsb): And now, we have a problem...
+ # The attach is async, so there's a window in which we don't see
+ # the attachment (until the attachment completes). We could also
+ # get problems with concurrent requests. I think we need an
+ # attachment state, and to write to the DB here, but that's a bigger
+ # change.
+ # For now, we'll probably have to rely on libraries being smart
+
+ # TODO(justinsb): How do I return "accepted" here?
+ return {'volumeAttachment': attachment}
+
+ def update(self, _req, _server_id, _id):
+ """Update a volume attachment. We don't currently support this."""
+ return faults.Fault(exc.HTTPBadRequest())
+
+ def delete(self, req, server_id, id):
+ """Detach a volume from an instance."""
+ context = req.environ['nova.context']
+
+ volume_id = id
+ LOG.audit(_("Detach volume %s"), volume_id, context=context)
+
+ try:
+ vol = self.volume_api.get(context, volume_id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ if str(vol['instance_id']) != server_id:
+ LOG.debug("instance_id != server_id")
+ return faults.Fault(exc.HTTPNotFound())
+
+ self.compute_api.detach_volume(context,
+ volume_id=volume_id)
+
+ return exc.HTTPAccepted()
+
+ def _items(self, req, server_id, entity_maker):
+ """Returns a list of attachments, transformed through entity_maker."""
+ context = req.environ['nova.context']
+
+ try:
+ instance = self.compute_api.get(context, server_id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ volumes = instance['volumes']
+ limited_list = common.limited(volumes, req)
+ res = [entity_maker(context, vol) for vol in limited_list]
+ return {'volumeAttachments': res}
+
+
+class Volumes(extensions.ExtensionDescriptor):
+ def get_name(self):
+ return "Volumes"
+
+ def get_alias(self):
+ return "VOLUMES"
+
+ def get_description(self):
+ return "Volumes support"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/volumes/api/v1.1"
+
+ def get_updated(self):
+ return "2011-03-25T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ # NOTE(justinsb): No way to provide singular name ('volume')
+ # Does this matter?
+ res = extensions.ResourceExtension('volumes',
+ VolumeController(),
+ collection_actions=
+ {'detail': 'GET'}
+ )
+ resources.append(res)
+
+ res = extensions.ResourceExtension('volume_attachments',
+ VolumeAttachmentController(),
+ parent=dict(
+ member_name='server',
+ collection_name='servers'))
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/incubator/volumes/__init__.py b/nova/api/openstack/incubator/volumes/__init__.py
deleted file mode 100644
index 2a9c93210..000000000
--- a/nova/api/openstack/incubator/volumes/__init__.py
+++ /dev/null
@@ -1,18 +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.import datetime
-
-"""The volumes extension adds volumes and attachments to the API."""
diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py
deleted file mode 100644
index aec4ea8f3..000000000
--- a/nova/api/openstack/incubator/volumes/volume_attachments.py
+++ /dev/null
@@ -1,184 +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.
-
-from webob import exc
-
-from nova import compute
-from nova import exception
-from nova import flags
-from nova import log as logging
-from nova import volume
-from nova import wsgi
-from nova.api.openstack import common
-from nova.api.openstack import faults
-
-
-LOG = logging.getLogger("nova.api.volumes")
-
-
-FLAGS = flags.FLAGS
-
-
-def _translate_detail_view(context, volume):
- """Maps keys for details view."""
-
- d = _translate_summary_view(context, volume)
-
- # No additional data / lookups at the moment
-
- return d
-
-
-def _translate_summary_view(context, vol):
- """Maps keys for 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['volumeId'] = volume_id
- if vol.get('instance_id'):
- d['serverId'] = vol['instance_id']
- if vol.get('mountpoint'):
- d['device'] = vol['mountpoint']
-
- return d
-
-
-class Controller(wsgi.Controller):
- """The volume attachment API controller for the Openstack API.
-
- A child resource of the server. Note that we use the volume id
- as the ID of the attachment (though this is not guaranteed externally)
-
- """
-
- _serialization_metadata = {
- 'application/xml': {
- 'attributes': {
- 'volumeAttachment': ['id',
- 'serverId',
- 'volumeId',
- 'device']}}}
-
- def __init__(self):
- self.compute_api = compute.API()
- self.volume_api = volume.API()
- super(Controller, self).__init__()
-
- def index(self, req, server_id):
- """Returns the list of volume attachments for a given instance."""
- return self._items(req, server_id,
- entity_maker=_translate_summary_view)
-
- def show(self, req, server_id, id):
- """Return data about the given volume."""
- context = req.environ['nova.context']
-
- volume_id = id
- try:
- vol = self.volume_api.get(context, volume_id)
- except exception.NotFound:
- LOG.debug("volume_id not found")
- return faults.Fault(exc.HTTPNotFound())
-
- if str(vol['instance_id']) != server_id:
- LOG.debug("instance_id != server_id")
- return faults.Fault(exc.HTTPNotFound())
-
- return {'volumeAttachment': _translate_detail_view(context, vol)}
-
- def create(self, req, server_id):
- """Attach a volume to an instance."""
- context = req.environ['nova.context']
-
- env = self._deserialize(req.body, req.get_content_type())
- if not env:
- return faults.Fault(exc.HTTPUnprocessableEntity())
-
- instance_id = server_id
- volume_id = env['volumeAttachment']['volumeId']
- device = env['volumeAttachment']['device']
-
- msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
- " at %(device)s") % locals()
- LOG.audit(msg, context=context)
-
- try:
- self.compute_api.attach_volume(context,
- instance_id=instance_id,
- volume_id=volume_id,
- device=device)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- # The attach is async
- attachment = {}
- attachment['id'] = volume_id
- attachment['volumeId'] = volume_id
-
- # NOTE(justinsb): And now, we have a problem...
- # The attach is async, so there's a window in which we don't see
- # the attachment (until the attachment completes). We could also
- # get problems with concurrent requests. I think we need an
- # attachment state, and to write to the DB here, but that's a bigger
- # change.
- # For now, we'll probably have to rely on libraries being smart
-
- # TODO(justinsb): How do I return "accepted" here?
- return {'volumeAttachment': attachment}
-
- def update(self, _req, _server_id, _id):
- """Update a volume attachment. We don't currently support this."""
- return faults.Fault(exc.HTTPBadRequest())
-
- def delete(self, req, server_id, id):
- """Detach a volume from an instance."""
- context = req.environ['nova.context']
-
- volume_id = id
- LOG.audit(_("Detach volume %s"), volume_id, context=context)
-
- try:
- vol = self.volume_api.get(context, volume_id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- if str(vol['instance_id']) != server_id:
- LOG.debug("instance_id != server_id")
- return faults.Fault(exc.HTTPNotFound())
-
- self.compute_api.detach_volume(context,
- volume_id=volume_id)
-
- return exc.HTTPAccepted()
-
- def _items(self, req, server_id, entity_maker):
- """Returns a list of attachments, transformed through entity_maker."""
- context = req.environ['nova.context']
-
- try:
- instance = self.compute_api.get(context, server_id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- volumes = instance['volumes']
- limited_list = common.limited(volumes, req)
- res = [entity_maker(context, vol) for vol in limited_list]
- return {'volumeAttachments': res}
diff --git a/nova/api/openstack/incubator/volumes/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py
deleted file mode 100644
index a7d5fbaa6..000000000
--- a/nova/api/openstack/incubator/volumes/volumes.py
+++ /dev/null
@@ -1,161 +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.
-
-from webob import exc
-
-from nova import exception
-from nova import flags
-from nova import log as logging
-from nova import volume
-from nova import wsgi
-from nova.api.openstack import common
-from nova.api.openstack import faults
-
-
-LOG = logging.getLogger("nova.api.volumes")
-
-
-FLAGS = flags.FLAGS
-
-
-def _translate_detail_view(context, vol):
- """Maps keys for details view."""
-
- d = _translate_summary_view(context, vol)
-
- # No additional data / lookups at the moment
-
- return d
-
-
-def _translate_summary_view(_context, vol):
- """Maps keys for summary view."""
- d = {}
-
- instance_id = None
- # instance_data = None
- attached_to = vol.get('instance')
- if attached_to:
- instance_id = attached_to['id']
- # instance_data = '%s[%s]' % (instance_ec2_id,
- # attached_to['host'])
- d['id'] = vol['id']
- d['status'] = vol['status']
- d['size'] = vol['size']
- d['availabilityZone'] = vol['availability_zone']
- d['createdAt'] = vol['created_at']
- # if context.is_admin:
- # v['status'] = '%s (%s, %s, %s, %s)' % (
- # vol['status'],
- # vol['user_id'],
- # vol['host'],
- # instance_data,
- # vol['mountpoint'])
- if vol['attach_status'] == 'attached':
- d['attachments'] = [{'attachTime': vol['attach_time'],
- 'deleteOnTermination': False,
- 'mountpoint': vol['mountpoint'],
- 'instanceId': instance_id,
- 'status': 'attached',
- 'volumeId': vol['id']}]
- else:
- d['attachments'] = [{}]
-
- d['displayName'] = vol['display_name']
- d['displayDescription'] = vol['display_description']
- return d
-
-
-class Controller(wsgi.Controller):
- """The Volumes API controller for the OpenStack API."""
-
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "volume": [
- "id",
- "status",
- "size",
- "availabilityZone",
- "createdAt",
- "displayName",
- "displayDescription",
- ]}}}
-
- def __init__(self):
- self.volume_api = volume.API()
- super(Controller, self).__init__()
-
- 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:
- return faults.Fault(exc.HTTPNotFound())
-
- return {'volume': _translate_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:
- self.volume_api.delete(context, volume_id=id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
- return exc.HTTPAccepted()
-
- def index(self, req):
- """Returns a summary list of volumes."""
- return self._items(req, entity_maker=_translate_summary_view)
-
- def detail(self, req):
- """Returns a detailed list of volumes."""
- return self._items(req, entity_maker=_translate_detail_view)
-
- def _items(self, req, entity_maker):
- """Returns a list of volumes, transformed through entity_maker."""
- context = req.environ['nova.context']
-
- volumes = self.volume_api.get_all(context)
- limited_list = common.limited(volumes, req)
- res = [entity_maker(context, vol) for vol in limited_list]
- return {'volumes': res}
-
- def create(self, req):
- """Creates a new volume."""
- context = req.environ['nova.context']
-
- env = self._deserialize(req.body, req.get_content_type())
- if not env:
- return faults.Fault(exc.HTTPUnprocessableEntity())
-
- vol = env['volume']
- size = vol['size']
- LOG.audit(_("Create volume of %s GB"), size, context=context)
- new_volume = self.volume_api.create(context, size,
- vol.get('display_name'),
- vol.get('display_description'))
-
- # Work around problem that instance is lazy-loaded...
- new_volume['instance'] = None
-
- retval = _translate_detail_view(context, new_volume)
-
- return {'volume': retval}
diff --git a/nova/api/openstack/incubator/volumes/volumes_ext.py b/nova/api/openstack/incubator/volumes/volumes_ext.py
deleted file mode 100644
index 6a3bb0265..000000000
--- a/nova/api/openstack/incubator/volumes/volumes_ext.py
+++ /dev/null
@@ -1,55 +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.
-
-from nova.api.openstack import extensions
-from nova.api.openstack.incubator.volumes import volumes
-from nova.api.openstack.incubator.volumes import volume_attachments
-
-
-class VolumesExtension(extensions.ExtensionDescriptor):
- def get_name(self):
- return "Volumes"
-
- def get_alias(self):
- return "VOLUMES"
-
- def get_description(self):
- return "Volumes support"
-
- def get_namespace(self):
- return "http://docs.openstack.org/ext/volumes/api/v1.1"
-
- def get_updated(self):
- return "2011-03-25T00:00:00+00:00"
-
- def get_resources(self):
- resources = []
-
- # NOTE(justinsb): No way to provide singular name ('volume')
- # Does this matter?
- res = extensions.ResourceExtension('volumes',
- volumes.Controller(),
- collection_actions={'detail': 'GET'}
- )
- resources.append(res)
-
- res = extensions.ResourceExtension('volume_attachments',
- volume_attachments.Controller(),
- parent=dict(
- member_name='server',
- collection_name='servers'))
- resources.append(res)
-
- return resources