volume api removed
This commit is contained in:
parent
7ba0c66512
commit
c112a41151
1383
.idea/workspace.xml
generated
1383
.idea/workspace.xml
generated
File diff suppressed because it is too large
Load Diff
@ -1,174 +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 manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila.openstack.common import log as logging
|
||||
from manila 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 _get(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _delete(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def validate_update(self, body):
|
||||
update = {}
|
||||
try:
|
||||
update['status'] = body['status']
|
||||
except (TypeError, KeyError):
|
||||
raise exc.HTTPBadRequest("Must specify 'status'")
|
||||
if update['status'] not in self.valid_status:
|
||||
raise exc.HTTPBadRequest("Must specify a valid status")
|
||||
return update
|
||||
|
||||
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['manila.context']
|
||||
self.authorize(context, 'reset_status')
|
||||
update = self.validate_update(body['os-reset_status'])
|
||||
msg = _("Updating %(resource)s '%(id)s' with '%(update)r'")
|
||||
LOG.debug(msg, {'resource': self.resource_name, 'id': id,
|
||||
'update': update})
|
||||
try:
|
||||
self._update(context, id, update)
|
||||
except exception.NotFound, e:
|
||||
raise exc.HTTPNotFound(e)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@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['manila.context']
|
||||
self.authorize(context, 'force_delete')
|
||||
try:
|
||||
resource = self._get(context, id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
self._delete(context, resource, force=True)
|
||||
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)
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
return self.volume_api.get(*args, **kwargs)
|
||||
|
||||
def _delete(self, *args, **kwargs):
|
||||
return self.volume_api.delete(*args, **kwargs)
|
||||
|
||||
def validate_update(self, body):
|
||||
update = super(VolumeAdminController, self).validate_update(body)
|
||||
if 'attach_status' in body:
|
||||
if body['attach_status'] not in ('detached', 'attached'):
|
||||
raise exc.HTTPBadRequest("Must specify a valid attach_status")
|
||||
update['attach_status'] = body['attach_status']
|
||||
return update
|
||||
|
||||
@wsgi.action('os-force_detach')
|
||||
def _force_detach(self, req, id, body):
|
||||
"""
|
||||
Roll back a bad detach after the volume been disconnected from
|
||||
the hypervisor.
|
||||
"""
|
||||
context = req.environ['manila.context']
|
||||
self.authorize(context, 'force_detach')
|
||||
try:
|
||||
volume = self._get(context, id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
self.volume_api.terminate_connection(context, volume,
|
||||
{}, force=True)
|
||||
self.volume_api.detach(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
|
||||
class SnapshotAdminController(AdminController):
|
||||
"""AdminController for Snapshots."""
|
||||
|
||||
collection = 'snapshots'
|
||||
|
||||
def _update(self, *args, **kwargs):
|
||||
db.snapshot_update(*args, **kwargs)
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
return self.volume_api.get_snapshot(*args, **kwargs)
|
||||
|
||||
def _delete(self, *args, **kwargs):
|
||||
return self.volume_api.delete_snapshot(*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
|
@ -1,125 +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.
|
||||
|
||||
"""The Extended Snapshot Attributes API extension."""
|
||||
|
||||
from webob import exc
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila import volume
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.soft_extension_authorizer(
|
||||
'volume',
|
||||
'extended_snapshot_attributes')
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributesController(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExtendedSnapshotAttributesController, self).__init__(*args,
|
||||
**kwargs)
|
||||
self.volume_api = volume.API()
|
||||
|
||||
def _get_snapshots(self, context):
|
||||
snapshots = self.volume_api.get_all_snapshots(context)
|
||||
rval = dict((snapshot['id'], snapshot) for snapshot in snapshots)
|
||||
return rval
|
||||
|
||||
def _extend_snapshot(self, context, snapshot, data):
|
||||
for attr in ['project_id', 'progress']:
|
||||
key = "%s:%s" % (Extended_snapshot_attributes.alias, attr)
|
||||
snapshot[key] = data[attr]
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=ExtendedSnapshotAttributeTemplate())
|
||||
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, id)
|
||||
except exception.NotFound:
|
||||
explanation = _("Snapshot not found.")
|
||||
raise exc.HTTPNotFound(explanation=explanation)
|
||||
|
||||
self._extend_snapshot(context, resp_obj.obj['snapshot'], snapshot)
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
# Attach our slave template to the response object
|
||||
resp_obj.attach(xml=ExtendedSnapshotAttributesTemplate())
|
||||
|
||||
snapshots = list(resp_obj.obj.get('snapshots', []))
|
||||
db_snapshots = self._get_snapshots(context)
|
||||
|
||||
for snapshot_object in snapshots:
|
||||
try:
|
||||
snapshot_data = db_snapshots[snapshot_object['id']]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
self._extend_snapshot(context, snapshot_object, snapshot_data)
|
||||
|
||||
|
||||
class Extended_snapshot_attributes(extensions.ExtensionDescriptor):
|
||||
"""Extended SnapshotAttributes support."""
|
||||
|
||||
name = "ExtendedSnapshotAttributes"
|
||||
alias = "os-extended-snapshot-attributes"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"extended_snapshot_attributes/api/v1")
|
||||
updated = "2012-06-19T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = ExtendedSnapshotAttributesController()
|
||||
extension = extensions.ControllerExtension(self, 'snapshots',
|
||||
controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_snapshot(elem):
|
||||
elem.set('{%s}project_id' % Extended_snapshot_attributes.namespace,
|
||||
'%s:project_id' % Extended_snapshot_attributes.alias)
|
||||
elem.set('{%s}progress' % Extended_snapshot_attributes.namespace,
|
||||
'%s:progress' % Extended_snapshot_attributes.alias)
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('snapshot', selector='snapshot')
|
||||
make_snapshot(root)
|
||||
alias = Extended_snapshot_attributes.alias
|
||||
namespace = Extended_snapshot_attributes.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class ExtendedSnapshotAttributesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('snapshots')
|
||||
elem = xmlutil.SubTemplateElement(root, 'snapshot',
|
||||
selector='snapshots')
|
||||
make_snapshot(elem)
|
||||
alias = Extended_snapshot_attributes.alias
|
||||
namespace = Extended_snapshot_attributes.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
@ -1,265 +0,0 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
"""The hosts admin extension."""
|
||||
|
||||
import webob.exc
|
||||
from xml.parsers import expat
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import timeutils
|
||||
from manila import utils
|
||||
from manila.volume import api as volume_api
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('volume', 'hosts')
|
||||
|
||||
|
||||
class HostIndexTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('hosts')
|
||||
elem = xmlutil.SubTemplateElement(root, 'host', selector='hosts')
|
||||
elem.set('service-status')
|
||||
elem.set('service')
|
||||
elem.set('zone')
|
||||
elem.set('service-state')
|
||||
elem.set('host_name')
|
||||
elem.set('last-update')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostUpdateTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
root.set('status')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostActionTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostShowTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
elem = xmlutil.make_flat_dict('resource', selector='host',
|
||||
subselector='resource')
|
||||
root.append(elem)
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class HostDeserializer(wsgi.XMLDeserializer):
|
||||
def default(self, string):
|
||||
try:
|
||||
node = utils.safe_minidom_parse_string(string)
|
||||
except expat.ExpatError:
|
||||
msg = _("cannot understand XML")
|
||||
raise exception.MalformedRequestBody(reason=msg)
|
||||
|
||||
updates = {}
|
||||
for child in node.childNodes[0].childNodes:
|
||||
updates[child.tagName] = self.extract_text(child)
|
||||
|
||||
return dict(body=updates)
|
||||
|
||||
|
||||
def _list_hosts(req, service=None):
|
||||
"""Returns a summary list of hosts."""
|
||||
curr_time = timeutils.utcnow()
|
||||
context = req.environ['manila.context']
|
||||
services = db.service_get_all(context, False)
|
||||
zone = ''
|
||||
if 'zone' in req.GET:
|
||||
zone = req.GET['zone']
|
||||
if zone:
|
||||
services = [s for s in services if s['availability_zone'] == zone]
|
||||
hosts = []
|
||||
for host in services:
|
||||
delta = curr_time - (host['updated_at'] or host['created_at'])
|
||||
alive = abs(utils.total_seconds(delta)) <= FLAGS.service_down_time
|
||||
status = (alive and "available") or "unavailable"
|
||||
active = 'enabled'
|
||||
if host['disabled']:
|
||||
active = 'disabled'
|
||||
LOG.debug('status, active and update: %s, %s, %s' %
|
||||
(status, active, host['updated_at']))
|
||||
hosts.append({'host_name': host['host'],
|
||||
'service': host['topic'],
|
||||
'zone': host['availability_zone'],
|
||||
'service-status': status,
|
||||
'service-state': active,
|
||||
'last-update': host['updated_at']})
|
||||
if service:
|
||||
hosts = [host for host in hosts
|
||||
if host["service"] == service]
|
||||
return hosts
|
||||
|
||||
|
||||
def check_host(fn):
|
||||
"""Makes sure that the host exists."""
|
||||
def wrapped(self, req, id, service=None, *args, **kwargs):
|
||||
listed_hosts = _list_hosts(req, service)
|
||||
hosts = [h["host_name"] for h in listed_hosts]
|
||||
if id in hosts:
|
||||
return fn(self, req, id, *args, **kwargs)
|
||||
else:
|
||||
message = _("Host '%s' could not be found.") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=message)
|
||||
return wrapped
|
||||
|
||||
|
||||
class HostController(object):
|
||||
"""The Hosts API controller for the OpenStack API."""
|
||||
def __init__(self):
|
||||
self.api = volume_api.HostAPI()
|
||||
super(HostController, self).__init__()
|
||||
|
||||
@wsgi.serializers(xml=HostIndexTemplate)
|
||||
def index(self, req):
|
||||
authorize(req.environ['manila.context'])
|
||||
return {'hosts': _list_hosts(req)}
|
||||
|
||||
@wsgi.serializers(xml=HostUpdateTemplate)
|
||||
@wsgi.deserializers(xml=HostDeserializer)
|
||||
@check_host
|
||||
def update(self, req, id, body):
|
||||
authorize(req.environ['manila.context'])
|
||||
update_values = {}
|
||||
for raw_key, raw_val in body.iteritems():
|
||||
key = raw_key.lower().strip()
|
||||
val = raw_val.lower().strip()
|
||||
if key == "status":
|
||||
if val in ("enable", "disable"):
|
||||
update_values['status'] = val.startswith("enable")
|
||||
else:
|
||||
explanation = _("Invalid status: '%s'") % raw_val
|
||||
raise webob.exc.HTTPBadRequest(explanation=explanation)
|
||||
else:
|
||||
explanation = _("Invalid update setting: '%s'") % raw_key
|
||||
raise webob.exc.HTTPBadRequest(explanation=explanation)
|
||||
update_setters = {'status': self._set_enabled_status}
|
||||
result = {}
|
||||
for key, value in update_values.iteritems():
|
||||
result.update(update_setters[key](req, id, value))
|
||||
return result
|
||||
|
||||
def _set_enabled_status(self, req, host, enabled):
|
||||
"""Sets the specified host's ability to accept new volumes."""
|
||||
context = req.environ['manila.context']
|
||||
state = "enabled" if enabled else "disabled"
|
||||
LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
|
||||
result = self.api.set_host_enabled(context,
|
||||
host=host,
|
||||
enabled=enabled)
|
||||
if result not in ("enabled", "disabled"):
|
||||
# An error message was returned
|
||||
raise webob.exc.HTTPBadRequest(explanation=result)
|
||||
return {"host": host, "status": result}
|
||||
|
||||
@wsgi.serializers(xml=HostShowTemplate)
|
||||
def show(self, req, id):
|
||||
"""Shows the volume usage info given by hosts.
|
||||
|
||||
:param context: security context
|
||||
:param host: hostname
|
||||
:returns: expected to use HostShowTemplate.
|
||||
ex.::
|
||||
|
||||
{'host': {'resource':D},..}
|
||||
D: {'host': 'hostname','project': 'admin',
|
||||
'volume_count': 1, 'total_volume_gb': 2048}
|
||||
"""
|
||||
host = id
|
||||
context = req.environ['manila.context']
|
||||
if not context.is_admin:
|
||||
msg = _("Describe-resource is admin only functionality")
|
||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
try:
|
||||
host_ref = db.service_get_by_host_and_topic(context,
|
||||
host,
|
||||
FLAGS.volume_topic)
|
||||
except exception.ServiceNotFound:
|
||||
raise webob.exc.HTTPNotFound(explanation=_("Host not found"))
|
||||
|
||||
# Getting total available/used resource
|
||||
# TODO(jdg): Add summary info for Snapshots
|
||||
volume_refs = db.volume_get_all_by_host(context, host_ref['host'])
|
||||
(count, sum) = db.volume_data_get_for_host(context,
|
||||
host_ref['host'])
|
||||
|
||||
snap_count_total = 0
|
||||
snap_sum_total = 0
|
||||
resources = [{'resource': {'host': host, 'project': '(total)',
|
||||
'volume_count': str(count),
|
||||
'total_volume_gb': str(sum),
|
||||
'snapshot_count': str(snap_count_total),
|
||||
'total_snapshot_gb': str(snap_sum_total)}}]
|
||||
|
||||
project_ids = [v['project_id'] for v in volume_refs]
|
||||
project_ids = list(set(project_ids))
|
||||
for project_id in project_ids:
|
||||
(count, sum) = db.volume_data_get_for_project(context, project_id)
|
||||
(snap_count, snap_sum) = db.snapshot_data_get_for_project(
|
||||
context,
|
||||
project_id)
|
||||
resources.append(
|
||||
{'resource':
|
||||
{'host': host,
|
||||
'project': project_id,
|
||||
'volume_count': str(count),
|
||||
'total_volume_gb': str(sum),
|
||||
'snapshot_count': str(snap_count),
|
||||
'total_snapshot_gb': str(snap_sum)}})
|
||||
snap_count_total += int(snap_count)
|
||||
snap_sum_total += int(snap_sum)
|
||||
resources[0]['resource']['snapshot_count'] = str(snap_count_total)
|
||||
resources[0]['resource']['total_snapshot_gb'] = str(snap_sum_total)
|
||||
return {"host": resources}
|
||||
|
||||
|
||||
class Hosts(extensions.ExtensionDescriptor):
|
||||
"""Admin-only host administration"""
|
||||
|
||||
name = "Hosts"
|
||||
alias = "os-hosts"
|
||||
namespace = "http://docs.openstack.org/volume/ext/hosts/api/v1.1"
|
||||
updated = "2011-06-29T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = [extensions.ResourceExtension('os-hosts',
|
||||
HostController(),
|
||||
collection_actions={
|
||||
'update': 'PUT'},
|
||||
member_actions={
|
||||
'startup': 'GET',
|
||||
'shutdown': 'GET',
|
||||
'reboot': 'GET'})]
|
||||
return resources
|
@ -1,162 +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 manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.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['manila.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['manila.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'extra_specs'):
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
self._check_type(context, type_id)
|
||||
|
||||
specs = body['extra_specs']
|
||||
db.volume_type_extra_specs_update_or_create(context,
|
||||
type_id,
|
||||
specs)
|
||||
notifier_info = dict(type_id=type_id, specs=specs)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.create',
|
||||
notifier_api.INFO, notifier_info)
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
||||
def update(self, req, type_id, id, body=None):
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
if not body:
|
||||
expl = _('Request body empty')
|
||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||
self._check_type(context, type_id)
|
||||
if id not 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)
|
||||
notifier_info = dict(type_id=type_id, id=id)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.update',
|
||||
notifier_api.INFO, notifier_info)
|
||||
return body
|
||||
|
||||
@wsgi.serializers(xml=VolumeTypeExtraSpecTemplate)
|
||||
def show(self, req, type_id, id):
|
||||
"""Return a single extra spec item."""
|
||||
context = req.environ['manila.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['manila.context']
|
||||
self._check_type(context, type_id)
|
||||
authorize(context)
|
||||
db.volume_type_extra_specs_delete(context, type_id, id)
|
||||
notifier_info = dict(type_id=type_id, id=id)
|
||||
notifier_api.notify(context, 'volumeTypeExtraSpecs',
|
||||
'volume_type_extra_specs.delete',
|
||||
notifier_api.INFO, notifier_info)
|
||||
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
|
@ -1,122 +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 manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.v1 import types
|
||||
from manila.api.views import types as views_types
|
||||
from manila import exception
|
||||
from manila.openstack.common.notifier import api as notifier_api
|
||||
from manila.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
|
||||
|
||||
def _notify_voloume_type_error(self, context, method, payload):
|
||||
notifier_api.notify(context,
|
||||
'volumeType',
|
||||
method,
|
||||
notifier_api.ERROR,
|
||||
payload)
|
||||
|
||||
@wsgi.action("create")
|
||||
@wsgi.serializers(xml=types.VolumeTypeTemplate)
|
||||
def _create(self, req, body):
|
||||
"""Creates a new volume type."""
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'volume_type'):
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
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.HTTPBadRequest()
|
||||
|
||||
try:
|
||||
volume_types.create(context, name, specs)
|
||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||
notifier_info = dict(volume_types=vol_type)
|
||||
notifier_api.notify(context, 'volumeType',
|
||||
'volume_type.create',
|
||||
notifier_api.INFO, notifier_info)
|
||||
|
||||
except exception.VolumeTypeExists as err:
|
||||
notifier_err = dict(volume_types=vol_type, error_message=str(err))
|
||||
self._notify_voloume_type_error(context,
|
||||
'volume_type.create',
|
||||
notifier_err)
|
||||
|
||||
raise webob.exc.HTTPConflict(explanation=str(err))
|
||||
except exception.NotFound as err:
|
||||
notifier_err = dict(volume_types=vol_type, error_message=str(err))
|
||||
self._notify_voloume_type_error(context,
|
||||
'volume_type.create',
|
||||
notifier_err)
|
||||
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['manila.context']
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
vol_type = volume_types.get_volume_type(context, id)
|
||||
volume_types.destroy(context, vol_type['id'])
|
||||
notifier_info = dict(volume_types=vol_type)
|
||||
notifier_api.notify(context, 'volumeType',
|
||||
'volume_type.delete',
|
||||
notifier_api.INFO, notifier_info)
|
||||
except exception.NotFound as err:
|
||||
notifier_err = dict(id=id, error_message=str(err))
|
||||
self._notify_voloume_type_error(context,
|
||||
'volume_type.delete',
|
||||
notifier_err)
|
||||
|
||||
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]
|
@ -1,204 +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 manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common.rpc import common as rpc_common
|
||||
from manila import utils
|
||||
from manila 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 = utils.safe_minidom_parse_string(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.action('os-attach')
|
||||
def _attach(self, req, id, body):
|
||||
"""Add attachment metadata."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
|
||||
instance_uuid = body['os-attach']['instance_uuid']
|
||||
mountpoint = body['os-attach']['mountpoint']
|
||||
|
||||
self.volume_api.attach(context, volume,
|
||||
instance_uuid, mountpoint)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-detach')
|
||||
def _detach(self, req, id, body):
|
||||
"""Clear attachment metadata."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.detach(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-reserve')
|
||||
def _reserve(self, req, id, body):
|
||||
"""Mark volume as reserved."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.reserve_volume(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-unreserve')
|
||||
def _unreserve(self, req, id, body):
|
||||
"""Unmark volume as reserved."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.unreserve_volume(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-begin_detaching')
|
||||
def _begin_detaching(self, req, id, body):
|
||||
"""Update volume status to 'detaching'."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.begin_detaching(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-roll_detaching')
|
||||
def _roll_detaching(self, req, id, body):
|
||||
"""Roll back volume status to 'in-use'."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.roll_detaching(context, volume)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.action('os-initialize_connection')
|
||||
def _initialize_connection(self, req, id, body):
|
||||
"""Initialize volume attachment."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
connector = body['os-initialize_connection']['connector']
|
||||
info = self.volume_api.initialize_connection(context,
|
||||
volume,
|
||||
connector)
|
||||
return {'connection_info': info}
|
||||
|
||||
@wsgi.action('os-terminate_connection')
|
||||
def _terminate_connection(self, req, id, body):
|
||||
"""Terminate volume attachment."""
|
||||
context = req.environ['manila.context']
|
||||
volume = self.volume_api.get(context, id)
|
||||
connector = body['os-terminate_connection']['connector']
|
||||
self.volume_api.terminate_connection(context, volume, connector)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@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['manila.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]
|
@ -1,93 +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.
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila.openstack.common import log as logging
|
||||
from manila import volume
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.soft_extension_authorizer('volume',
|
||||
'volume_host_attribute')
|
||||
|
||||
|
||||
class VolumeHostAttributeController(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VolumeHostAttributeController, self).__init__(*args, **kwargs)
|
||||
self.volume_api = volume.API()
|
||||
|
||||
def _add_volume_host_attribute(self, context, resp_volume):
|
||||
try:
|
||||
db_volume = self.volume_api.get(context, resp_volume['id'])
|
||||
except Exception:
|
||||
return
|
||||
else:
|
||||
key = "%s:host" % Volume_host_attribute.alias
|
||||
resp_volume[key] = db_volume['host']
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeHostAttributeTemplate())
|
||||
self._add_volume_host_attribute(context, resp_obj.obj['volume'])
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeListHostAttributeTemplate())
|
||||
for volume in list(resp_obj.obj['volumes']):
|
||||
self._add_volume_host_attribute(context, volume)
|
||||
|
||||
|
||||
class Volume_host_attribute(extensions.ExtensionDescriptor):
|
||||
"""Expose host as an attribute of a volume."""
|
||||
|
||||
name = "VolumeHostAttribute"
|
||||
alias = "os-vol-host-attr"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_host_attribute/api/v1")
|
||||
updated = "2011-11-03T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeHostAttributeController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('{%s}host' % Volume_host_attribute.namespace,
|
||||
'%s:host' % Volume_host_attribute.alias)
|
||||
|
||||
|
||||
class VolumeHostAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
alias = Volume_host_attribute.alias
|
||||
namespace = Volume_host_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeListHostAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
alias = Volume_host_attribute.alias
|
||||
namespace = Volume_host_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
@ -1,106 +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.
|
||||
|
||||
"""The Volume Image Metadata API extension."""
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import volume
|
||||
|
||||
|
||||
authorize = extensions.soft_extension_authorizer('volume',
|
||||
'volume_image_metadata')
|
||||
|
||||
|
||||
class VolumeImageMetadataController(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VolumeImageMetadataController, self).__init__(*args, **kwargs)
|
||||
self.volume_api = volume.API()
|
||||
|
||||
def _add_image_metadata(self, context, resp_volume):
|
||||
try:
|
||||
image_meta = self.volume_api.get_volume_image_metadata(
|
||||
context, resp_volume)
|
||||
except Exception:
|
||||
return
|
||||
else:
|
||||
if image_meta:
|
||||
resp_volume['volume_image_metadata'] = dict(
|
||||
image_meta.iteritems())
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeImageMetadataTemplate())
|
||||
self._add_image_metadata(context, resp_obj.obj['volume'])
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumesImageMetadataTemplate())
|
||||
for volume in list(resp_obj.obj.get('volumes', [])):
|
||||
self._add_image_metadata(context, volume)
|
||||
|
||||
|
||||
class Volume_image_metadata(extensions.ExtensionDescriptor):
|
||||
"""Show image metadata associated with the volume"""
|
||||
|
||||
name = "VolumeImageMetadata"
|
||||
alias = "os-vol-image-meta"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_image_metadata/api/v1")
|
||||
updated = "2012-12-07T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeImageMetadataController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
class VolumeImageMetadataMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume_image_metadata',
|
||||
selector='volume_image_metadata')
|
||||
elem = xmlutil.SubTemplateElement(root, 'meta',
|
||||
selector=xmlutil.get_items)
|
||||
elem.set('key', 0)
|
||||
elem.text = 1
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class VolumeImageMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
root.append(VolumeImageMetadataMetadataTemplate())
|
||||
|
||||
alias = Volume_image_metadata.alias
|
||||
namespace = Volume_image_metadata.namespace
|
||||
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumesImageMetadataTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volume')
|
||||
elem.append(VolumeImageMetadataMetadataTemplate())
|
||||
|
||||
alias = Volume_image_metadata.alias
|
||||
namespace = Volume_image_metadata.namespace
|
||||
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
@ -1,91 +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.
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import volume
|
||||
|
||||
|
||||
authorize = extensions.soft_extension_authorizer('volume',
|
||||
'volume_tenant_attribute')
|
||||
|
||||
|
||||
class VolumeTenantAttributeController(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VolumeTenantAttributeController, self).__init__(*args, **kwargs)
|
||||
self.volume_api = volume.API()
|
||||
|
||||
def _add_volume_tenant_attribute(self, context, resp_volume):
|
||||
try:
|
||||
db_volume = self.volume_api.get(context, resp_volume['id'])
|
||||
except Exception:
|
||||
return
|
||||
else:
|
||||
key = "%s:tenant_id" % Volume_tenant_attribute.alias
|
||||
resp_volume[key] = db_volume['project_id']
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeTenantAttributeTemplate())
|
||||
self._add_volume_tenant_attribute(context, resp_obj.obj['volume'])
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['manila.context']
|
||||
if authorize(context):
|
||||
resp_obj.attach(xml=VolumeListTenantAttributeTemplate())
|
||||
for volume in list(resp_obj.obj['volumes']):
|
||||
self._add_volume_tenant_attribute(context, volume)
|
||||
|
||||
|
||||
class Volume_tenant_attribute(extensions.ExtensionDescriptor):
|
||||
"""Expose the internal project_id as an attribute of a volume."""
|
||||
|
||||
name = "VolumeTenantAttribute"
|
||||
alias = "os-vol-tenant-attr"
|
||||
namespace = ("http://docs.openstack.org/volume/ext/"
|
||||
"volume_tenant_attribute/api/v1")
|
||||
updated = "2011-11-03T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = VolumeTenantAttributeController()
|
||||
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
||||
return [extension]
|
||||
|
||||
|
||||
def make_volume(elem):
|
||||
elem.set('{%s}tenant_id' % Volume_tenant_attribute.namespace,
|
||||
'%s:tenant_id' % Volume_tenant_attribute.alias)
|
||||
|
||||
|
||||
class VolumeTenantAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
alias = Volume_tenant_attribute.alias
|
||||
namespace = Volume_tenant_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
||||
|
||||
|
||||
class VolumeListTenantAttributeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volumes')
|
||||
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
||||
make_volume(elem)
|
||||
alias = Volume_tenant_attribute.alias
|
||||
namespace = Volume_tenant_attribute.namespace
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
@ -24,11 +24,6 @@ WSGI middleware for OpenStack Volume API.
|
||||
from manila.api import extensions
|
||||
import manila.api.openstack
|
||||
from manila.api.v1 import limits
|
||||
from manila.api.v1 import snapshot_metadata
|
||||
from manila.api.v1 import snapshots
|
||||
from manila.api.v1 import types
|
||||
from manila.api.v1 import volume_metadata
|
||||
from manila.api.v1 import volumes
|
||||
from manila.api import versions
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
@ -50,46 +45,3 @@ class APIRouter(manila.api.openstack.APIRouter):
|
||||
action='show')
|
||||
|
||||
mapper.redirect("", "/")
|
||||
|
||||
self.resources['volumes'] = volumes.create_resource(ext_mgr)
|
||||
mapper.resource("volume", "volumes",
|
||||
controller=self.resources['volumes'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
||||
self.resources['types'] = types.create_resource()
|
||||
mapper.resource("type", "types",
|
||||
controller=self.resources['types'])
|
||||
|
||||
self.resources['snapshots'] = snapshots.create_resource(ext_mgr)
|
||||
mapper.resource("snapshot", "snapshots",
|
||||
controller=self.resources['snapshots'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
||||
self.resources['snapshot_metadata'] = \
|
||||
snapshot_metadata.create_resource()
|
||||
snapshot_metadata_controller = self.resources['snapshot_metadata']
|
||||
|
||||
mapper.resource("snapshot_metadata", "metadata",
|
||||
controller=snapshot_metadata_controller,
|
||||
parent_resource=dict(member_name='snapshot',
|
||||
collection_name='snapshots'))
|
||||
|
||||
self.resources['limits'] = limits.create_resource()
|
||||
mapper.resource("limit", "limits",
|
||||
controller=self.resources['limits'])
|
||||
self.resources['volume_metadata'] = \
|
||||
volume_metadata.create_resource()
|
||||
volume_metadata_controller = self.resources['volume_metadata']
|
||||
|
||||
mapper.resource("volume_metadata", "metadata",
|
||||
controller=volume_metadata_controller,
|
||||
parent_resource=dict(member_name='volume',
|
||||
collection_name='volumes'))
|
||||
|
||||
mapper.connect("metadata",
|
||||
"/{project_id}/volumes/{volume_id}/metadata",
|
||||
controller=volume_metadata_controller,
|
||||
action='update_all',
|
||||
conditions={"method": ['PUT']})
|
||||
|
@ -1,164 +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.
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila import exception
|
||||
from manila import volume
|
||||
from webob import exc
|
||||
|
||||
|
||||
class Controller(object):
|
||||
""" The volume metadata API controller for the OpenStack API """
|
||||
|
||||
def __init__(self):
|
||||
self.volume_api = volume.API()
|
||||
super(Controller, self).__init__()
|
||||
|
||||
def _get_metadata(self, context, snapshot_id):
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
meta = self.volume_api.get_snapshot_metadata(context, snapshot)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, snapshot_id):
|
||||
""" Returns the list of metadata for a given snapshot"""
|
||||
context = req.environ['manila.context']
|
||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
|
||||
new_metadata = self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
metadata,
|
||||
delete=False)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, snapshot_id, id, body):
|
||||
try:
|
||||
meta_item = body['meta']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if id not in meta_item:
|
||||
expl = _('Request body and URI mismatch')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if len(meta_item) > 1:
|
||||
expl = _('Request body contains too many items')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
meta_item,
|
||||
delete=False)
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
new_metadata = self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
metadata,
|
||||
delete=True)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
def _update_snapshot_metadata(self, context,
|
||||
snapshot_id, metadata,
|
||||
delete=False):
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
return self.volume_api.update_snapshot_metadata(context,
|
||||
snapshot,
|
||||
metadata,
|
||||
delete)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
except (ValueError, AttributeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
except exception.InvalidVolumeMetadata as error:
|
||||
raise exc.HTTPBadRequest(explanation=unicode(error))
|
||||
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=unicode(error))
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, snapshot_id, id):
|
||||
""" Return a single metadata item """
|
||||
context = req.environ['manila.context']
|
||||
data = self._get_metadata(context, snapshot_id)
|
||||
|
||||
try:
|
||||
return {'meta': {id: data[id]}}
|
||||
except KeyError:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
def delete(self, req, snapshot_id, id):
|
||||
""" Deletes an existing metadata """
|
||||
context = req.environ['manila.context']
|
||||
|
||||
metadata = self._get_metadata(context, snapshot_id)
|
||||
|
||||
if id not in metadata:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
self.volume_api.delete_snapshot_metadata(context, snapshot, id)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return webob.Response(status_int=200)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
@ -1,234 +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 manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.v1 import volumes
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import strutils
|
||||
from manila import utils
|
||||
from manila import volume
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
def _translate_snapshot_detail_view(context, snapshot):
|
||||
"""Maps keys for snapshots details view."""
|
||||
|
||||
d = _translate_snapshot_summary_view(context, snapshot)
|
||||
|
||||
# NOTE(gagupta): No additional data / lookups at the moment
|
||||
return d
|
||||
|
||||
|
||||
def _translate_snapshot_summary_view(context, snapshot):
|
||||
"""Maps keys for snapshots summary view."""
|
||||
d = {}
|
||||
|
||||
d['id'] = snapshot['id']
|
||||
d['created_at'] = snapshot['created_at']
|
||||
d['display_name'] = snapshot['display_name']
|
||||
d['display_description'] = snapshot['display_description']
|
||||
d['volume_id'] = snapshot['volume_id']
|
||||
d['status'] = snapshot['status']
|
||||
d['size'] = snapshot['volume_size']
|
||||
|
||||
if snapshot.get('snapshot_metadata'):
|
||||
metadata = snapshot.get('snapshot_metadata')
|
||||
d['metadata'] = dict((item['key'], item['value']) for item in metadata)
|
||||
# avoid circular ref when vol is a Volume instance
|
||||
elif snapshot.get('metadata') and isinstance(snapshot.get('metadata'),
|
||||
dict):
|
||||
d['metadata'] = snapshot['metadata']
|
||||
else:
|
||||
d['metadata'] = {}
|
||||
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')
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
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['manila.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['manila.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['manila.context']
|
||||
|
||||
search_opts = {}
|
||||
search_opts.update(req.GET)
|
||||
allowed_search_options = ('status', 'volume_id', 'display_name')
|
||||
volumes.remove_invalid_options(context, search_opts,
|
||||
allowed_search_options)
|
||||
|
||||
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."""
|
||||
kwargs = {}
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not self.is_valid_body(body, 'snapshot'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
snapshot = body['snapshot']
|
||||
kwargs['metadata'] = snapshot.get('metadata', None)
|
||||
|
||||
volume_id = snapshot['volume_id']
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
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 strutils.bool_from_string(force):
|
||||
new_snapshot = self.volume_api.create_snapshot_force(
|
||||
context,
|
||||
volume,
|
||||
snapshot.get('display_name'),
|
||||
snapshot.get('display_description'),
|
||||
**kwargs)
|
||||
else:
|
||||
new_snapshot = self.volume_api.create_snapshot(
|
||||
context,
|
||||
volume,
|
||||
snapshot.get('display_name'),
|
||||
snapshot.get('display_description'),
|
||||
**kwargs)
|
||||
|
||||
retval = _translate_snapshot_detail_view(context, new_snapshot)
|
||||
|
||||
return {'snapshot': retval}
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a snapshot."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not body:
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
if 'snapshot' not in body:
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
snapshot = body['snapshot']
|
||||
update_dict = {}
|
||||
|
||||
valid_update_keys = (
|
||||
'display_name',
|
||||
'display_description',
|
||||
)
|
||||
|
||||
for key in valid_update_keys:
|
||||
if key in snapshot:
|
||||
update_dict[key] = snapshot[key]
|
||||
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, id)
|
||||
self.volume_api.update_snapshot(context, snapshot, update_dict)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
snapshot.update(update_dict)
|
||||
|
||||
return {'snapshot': _translate_snapshot_detail_view(context, snapshot)}
|
||||
|
||||
|
||||
def create_resource(ext_mgr):
|
||||
return wsgi.Resource(SnapshotsController(ext_mgr))
|
@ -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 manila.api.openstack import wsgi
|
||||
from manila.api.views import types as views_types
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila.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['manila.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['manila.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())
|
@ -1,164 +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.
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila import exception
|
||||
from manila import volume
|
||||
from webob import exc
|
||||
|
||||
|
||||
class Controller(object):
|
||||
""" The volume metadata API controller for the OpenStack API """
|
||||
|
||||
def __init__(self):
|
||||
self.volume_api = volume.API()
|
||||
super(Controller, self).__init__()
|
||||
|
||||
def _get_metadata(self, context, volume_id):
|
||||
try:
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
meta = self.volume_api.get_volume_metadata(context, volume)
|
||||
except exception.VolumeNotFound:
|
||||
msg = _('volume does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, volume_id):
|
||||
""" Returns the list of metadata for a given volume"""
|
||||
context = req.environ['manila.context']
|
||||
return {'metadata': self._get_metadata(context, volume_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, volume_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
|
||||
new_metadata = self._update_volume_metadata(context,
|
||||
volume_id,
|
||||
metadata,
|
||||
delete=False)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, volume_id, id, body):
|
||||
try:
|
||||
meta_item = body['meta']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if id not in meta_item:
|
||||
expl = _('Request body and URI mismatch')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if len(meta_item) > 1:
|
||||
expl = _('Request body contains too many items')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
self._update_volume_metadata(context,
|
||||
volume_id,
|
||||
meta_item,
|
||||
delete=False)
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, volume_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
new_metadata = self._update_volume_metadata(context,
|
||||
volume_id,
|
||||
metadata,
|
||||
delete=True)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
def _update_volume_metadata(self, context,
|
||||
volume_id, metadata,
|
||||
delete=False):
|
||||
try:
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
return self.volume_api.update_volume_metadata(context,
|
||||
volume,
|
||||
metadata,
|
||||
delete)
|
||||
except exception.VolumeNotFound:
|
||||
msg = _('volume does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
except (ValueError, AttributeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
except exception.InvalidVolumeMetadata as error:
|
||||
raise exc.HTTPBadRequest(explanation=unicode(error))
|
||||
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=unicode(error))
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, volume_id, id):
|
||||
""" Return a single metadata item """
|
||||
context = req.environ['manila.context']
|
||||
data = self._get_metadata(context, volume_id)
|
||||
|
||||
try:
|
||||
return {'meta': {id: data[id]}}
|
||||
except KeyError:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
def delete(self, req, volume_id, id):
|
||||
""" Deletes an existing metadata """
|
||||
context = req.environ['manila.context']
|
||||
|
||||
metadata = self._get_metadata(context, volume_id)
|
||||
|
||||
if id not in metadata:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
self.volume_api.delete_volume_metadata(context, volume, id)
|
||||
except exception.VolumeNotFound:
|
||||
msg = _('volume does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return webob.Response(status_int=200)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
@ -1,421 +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 manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import uuidutils
|
||||
from manila import utils
|
||||
from manila import volume
|
||||
from manila.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']
|
||||
d['source_volid'] = vol['source_volid']
|
||||
|
||||
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)
|
||||
# avoid circular ref when vol is a Volume instance
|
||||
elif vol.get('metadata') and isinstance(vol.get('metadata'), dict):
|
||||
d['metadata'] = vol['metadata']
|
||||
else:
|
||||
d['metadata'] = {}
|
||||
|
||||
if vol.get('volume_glance_metadata'):
|
||||
d['bootable'] = 'true'
|
||||
else:
|
||||
d['bootable'] = 'false'
|
||||
|
||||
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')
|
||||
elem.set('source_volid')
|
||||
|
||||
attachments = xmlutil.SubTemplateElement(elem, 'attachments')
|
||||
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
|
||||
selector='attachments')
|
||||
make_attachment(attachment)
|
||||
|
||||
# Attach metadata node
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
volume_nsmap = {None: xmlutil.XMLNS_VOLUME_V1, 'atom': xmlutil.XMLNS_ATOM}
|
||||
|
||||
|
||||
class VolumeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
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, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
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 = utils.safe_minidom_parse_string(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['manila.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['manila.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['manila.context']
|
||||
remove_invalid_options(context,
|
||||
search_opts, self._get_volume_search_options())
|
||||
|
||||
volumes = self.volume_api.get_all(context, marker=None, limit=None,
|
||||
sort_key='created_at',
|
||||
sort_dir='desc', filters=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 uuidutils.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'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
context = req.environ['manila.context']
|
||||
volume = body['volume']
|
||||
|
||||
kwargs = {}
|
||||
|
||||
req_volume_type = volume.get('volume_type', None)
|
||||
if req_volume_type:
|
||||
if not uuidutils.is_uuid_like(req_volume_type):
|
||||
try:
|
||||
kwargs['volume_type'] = \
|
||||
volume_types.get_volume_type_by_name(
|
||||
context, req_volume_type)
|
||||
except exception.VolumeTypeNotFound:
|
||||
explanation = 'Volume type not found.'
|
||||
raise exc.HTTPNotFound(explanation=explanation)
|
||||
else:
|
||||
try:
|
||||
kwargs['volume_type'] = volume_types.get_volume_type(
|
||||
context, req_volume_type)
|
||||
except exception.VolumeTypeNotFound:
|
||||
explanation = 'Volume type not found.'
|
||||
raise exc.HTTPNotFound(explanation=explanation)
|
||||
|
||||
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
|
||||
|
||||
source_volid = volume.get('source_volid')
|
||||
if source_volid is not None:
|
||||
kwargs['source_volume'] = self.volume_api.get_volume(context,
|
||||
source_volid)
|
||||
else:
|
||||
kwargs['source_volume'] = None
|
||||
|
||||
size = volume.get('size', None)
|
||||
if size is None and kwargs['snapshot'] is not None:
|
||||
size = kwargs['snapshot']['volume_size']
|
||||
elif size is None and kwargs['source_volume'] is not None:
|
||||
size = kwargs['source_volume']['size']
|
||||
|
||||
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 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.iteritems()),
|
||||
image_uuid)
|
||||
|
||||
return {'volume': retval}
|
||||
|
||||
def _get_volume_search_options(self):
|
||||
"""Return volume search options allowed by non-admin."""
|
||||
return ('display_name', 'status')
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a volume."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not body:
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
if 'volume' not in body:
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
volume = body['volume']
|
||||
update_dict = {}
|
||||
|
||||
valid_update_keys = (
|
||||
'display_name',
|
||||
'display_description',
|
||||
'metadata',
|
||||
)
|
||||
|
||||
for key in valid_update_keys:
|
||||
if key in volume:
|
||||
update_dict[key] = volume[key]
|
||||
|
||||
try:
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.update(context, volume, update_dict)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
volume.update(update_dict)
|
||||
|
||||
return {'volume': _translate_volume_detail_view(context, volume)}
|
||||
|
||||
|
||||
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:
|
||||
del search_options[opt]
|
@ -24,9 +24,6 @@ WSGI middleware for OpenStack Volume API.
|
||||
from manila.api import extensions
|
||||
import manila.api.openstack
|
||||
from manila.api.v2 import limits
|
||||
from manila.api.v2 import snapshots
|
||||
from manila.api.v2 import types
|
||||
from manila.api.v2 import volumes
|
||||
from manila.api import versions
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
@ -47,24 +44,4 @@ class APIRouter(manila.api.openstack.APIRouter):
|
||||
controller=self.resources['versions'],
|
||||
action='show')
|
||||
|
||||
mapper.redirect("", "/")
|
||||
|
||||
self.resources['volumes'] = volumes.create_resource(ext_mgr)
|
||||
mapper.resource("volume", "volumes",
|
||||
controller=self.resources['volumes'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
||||
self.resources['types'] = types.create_resource()
|
||||
mapper.resource("type", "types",
|
||||
controller=self.resources['types'])
|
||||
|
||||
self.resources['snapshots'] = snapshots.create_resource(ext_mgr)
|
||||
mapper.resource("snapshot", "snapshots",
|
||||
controller=self.resources['snapshots'],
|
||||
collection={'detail': 'GET'},
|
||||
member={'action': 'POST'})
|
||||
|
||||
self.resources['limits'] = limits.create_resource()
|
||||
mapper.resource("limit", "limits",
|
||||
controller=self.resources['limits'])
|
||||
mapper.redirect("", "/")
|
@ -1,164 +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.
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila import exception
|
||||
from manila import volume
|
||||
from webob import exc
|
||||
|
||||
|
||||
class Controller(object):
|
||||
""" The volume metadata API controller for the OpenStack API """
|
||||
|
||||
def __init__(self):
|
||||
self.volume_api = volume.API()
|
||||
super(Controller, self).__init__()
|
||||
|
||||
def _get_metadata(self, context, snapshot_id):
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
meta = self.volume_api.get_snapshot_metadata(context, snapshot)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return meta
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
def index(self, req, snapshot_id):
|
||||
""" Returns the list of metadata for a given snapshot"""
|
||||
context = req.environ['manila.context']
|
||||
return {'metadata': self._get_metadata(context, snapshot_id)}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def create(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
|
||||
new_metadata = self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
metadata,
|
||||
delete=False)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
@wsgi.deserializers(xml=common.MetaItemDeserializer)
|
||||
def update(self, req, snapshot_id, id, body):
|
||||
try:
|
||||
meta_item = body['meta']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if id not in meta_item:
|
||||
expl = _('Request body and URI mismatch')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
if len(meta_item) > 1:
|
||||
expl = _('Request body contains too many items')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
meta_item,
|
||||
delete=False)
|
||||
|
||||
return {'meta': meta_item}
|
||||
|
||||
@wsgi.serializers(xml=common.MetadataTemplate)
|
||||
@wsgi.deserializers(xml=common.MetadataDeserializer)
|
||||
def update_all(self, req, snapshot_id, body):
|
||||
try:
|
||||
metadata = body['metadata']
|
||||
except (TypeError, KeyError):
|
||||
expl = _('Malformed request body')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
context = req.environ['manila.context']
|
||||
new_metadata = self._update_snapshot_metadata(context,
|
||||
snapshot_id,
|
||||
metadata,
|
||||
delete=True)
|
||||
|
||||
return {'metadata': new_metadata}
|
||||
|
||||
def _update_snapshot_metadata(self, context,
|
||||
snapshot_id, metadata,
|
||||
delete=False):
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
return self.volume_api.update_snapshot_metadata(context,
|
||||
snapshot,
|
||||
metadata,
|
||||
delete)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
except (ValueError, AttributeError):
|
||||
msg = _("Malformed request body")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
except exception.InvalidVolumeMetadata as error:
|
||||
raise exc.HTTPBadRequest(explanation=unicode(error))
|
||||
|
||||
except exception.InvalidVolumeMetadataSize as error:
|
||||
raise exc.HTTPRequestEntityTooLarge(explanation=unicode(error))
|
||||
|
||||
@wsgi.serializers(xml=common.MetaItemTemplate)
|
||||
def show(self, req, snapshot_id, id):
|
||||
""" Return a single metadata item """
|
||||
context = req.environ['manila.context']
|
||||
data = self._get_metadata(context, snapshot_id)
|
||||
|
||||
try:
|
||||
return {'meta': {id: data[id]}}
|
||||
except KeyError:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
def delete(self, req, snapshot_id, id):
|
||||
""" Deletes an existing metadata """
|
||||
context = req.environ['manila.context']
|
||||
|
||||
metadata = self._get_metadata(context, snapshot_id)
|
||||
|
||||
if id not in metadata:
|
||||
msg = _("Metadata item was not found")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
self.volume_api.delete_snapshot_metadata(context, snapshot, id)
|
||||
except exception.SnapshotNotFound:
|
||||
msg = _('snapshot does not exist')
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
return webob.Response(status_int=200)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
@ -1,257 +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 manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.v2 import volumes
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import strutils
|
||||
from manila import utils
|
||||
from manila import volume
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
def _translate_snapshot_detail_view(context, snapshot):
|
||||
"""Maps keys for snapshots details view."""
|
||||
|
||||
d = _translate_snapshot_summary_view(context, snapshot)
|
||||
|
||||
# NOTE(gagupta): No additional data / lookups at the moment
|
||||
return d
|
||||
|
||||
|
||||
def _translate_snapshot_summary_view(context, snapshot):
|
||||
"""Maps keys for snapshots summary view."""
|
||||
d = {}
|
||||
|
||||
d['id'] = snapshot['id']
|
||||
d['created_at'] = snapshot['created_at']
|
||||
d['name'] = snapshot['display_name']
|
||||
d['description'] = snapshot['display_description']
|
||||
d['volume_id'] = snapshot['volume_id']
|
||||
d['status'] = snapshot['status']
|
||||
d['size'] = snapshot['volume_size']
|
||||
|
||||
if snapshot.get('snapshot_metadata'):
|
||||
metadata = snapshot.get('snapshot_metadata')
|
||||
d['metadata'] = dict((item['key'], item['value']) for item in metadata)
|
||||
# avoid circular ref when vol is a Volume instance
|
||||
elif snapshot.get('metadata') and isinstance(snapshot.get('metadata'),
|
||||
dict):
|
||||
d['metadata'] = snapshot['metadata']
|
||||
else:
|
||||
d['metadata'] = {}
|
||||
return d
|
||||
|
||||
|
||||
def make_snapshot(elem):
|
||||
elem.set('id')
|
||||
elem.set('status')
|
||||
elem.set('size')
|
||||
elem.set('created_at')
|
||||
elem.set('name')
|
||||
elem.set('description')
|
||||
elem.set('volume_id')
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
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['manila.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['manila.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['manila.context']
|
||||
|
||||
search_opts = {}
|
||||
search_opts.update(req.GET)
|
||||
allowed_search_options = ('status', 'volume_id', 'name')
|
||||
volumes.remove_invalid_options(context, search_opts,
|
||||
allowed_search_options)
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in search_opts:
|
||||
search_opts['display_name'] = search_opts['name']
|
||||
del search_opts['name']
|
||||
|
||||
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.response(202)
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def create(self, req, body):
|
||||
"""Creates a new snapshot."""
|
||||
kwargs = {}
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not self.is_valid_body(body, 'snapshot'):
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
snapshot = body['snapshot']
|
||||
kwargs['metadata'] = snapshot.get('metadata', None)
|
||||
|
||||
volume_id = snapshot['volume_id']
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
force = snapshot.get('force', False)
|
||||
msg = _("Create snapshot from volume %s")
|
||||
LOG.audit(msg, volume_id, context=context)
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in snapshot:
|
||||
snapshot['display_name'] = snapshot.get('name')
|
||||
del snapshot['name']
|
||||
|
||||
if not utils.is_valid_boolstr(force):
|
||||
msg = _("Invalid value '%s' for force. ") % force
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
if strutils.bool_from_string(force):
|
||||
new_snapshot = self.volume_api.create_snapshot_force(
|
||||
context,
|
||||
volume,
|
||||
snapshot.get('display_name'),
|
||||
snapshot.get('description'),
|
||||
**kwargs)
|
||||
else:
|
||||
new_snapshot = self.volume_api.create_snapshot(
|
||||
context,
|
||||
volume,
|
||||
snapshot.get('display_name'),
|
||||
snapshot.get('description'),
|
||||
**kwargs)
|
||||
|
||||
retval = _translate_snapshot_detail_view(context, new_snapshot)
|
||||
|
||||
return {'snapshot': retval}
|
||||
|
||||
@wsgi.serializers(xml=SnapshotTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a snapshot."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not body:
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
if 'snapshot' not in body:
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
snapshot = body['snapshot']
|
||||
update_dict = {}
|
||||
|
||||
valid_update_keys = (
|
||||
'name',
|
||||
'description',
|
||||
'display_description',
|
||||
)
|
||||
|
||||
# NOTE(thingee): v2 API allows description instead of
|
||||
# display_description
|
||||
if 'description' in snapshot:
|
||||
snapshot['display_description'] = snapshot['description']
|
||||
del snapshot['description']
|
||||
|
||||
for key in valid_update_keys:
|
||||
if key in snapshot:
|
||||
update_dict[key] = snapshot[key]
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in update_dict:
|
||||
update_dict['display_name'] = update_dict['name']
|
||||
del update_dict['name']
|
||||
|
||||
try:
|
||||
snapshot = self.volume_api.get_snapshot(context, id)
|
||||
self.volume_api.update_snapshot(context, snapshot, update_dict)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
snapshot.update(update_dict)
|
||||
|
||||
return {'snapshot': _translate_snapshot_detail_view(context, snapshot)}
|
||||
|
||||
|
||||
def create_resource(ext_mgr):
|
||||
return wsgi.Resource(SnapshotsController(ext_mgr))
|
@ -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 manila.api.openstack import wsgi
|
||||
from manila.api.views import types as views_types
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila.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['manila.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['manila.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())
|
@ -1,122 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 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.
|
||||
|
||||
from manila.api import common
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
"""Model a server API response as a python dictionary."""
|
||||
|
||||
_collection_name = "volumes"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize view builder."""
|
||||
super(ViewBuilder, self).__init__()
|
||||
|
||||
def summary_list(self, request, volumes):
|
||||
"""Show a list of volumes without many details."""
|
||||
return self._list_view(self.summary, request, volumes)
|
||||
|
||||
def detail_list(self, request, volumes):
|
||||
"""Detailed view of a list of volumes."""
|
||||
return self._list_view(self.detail, request, volumes)
|
||||
|
||||
def summary(self, request, volume):
|
||||
"""Generic, non-detailed view of an volume."""
|
||||
return {
|
||||
'volume': {
|
||||
'id': volume['id'],
|
||||
'name': volume['display_name'],
|
||||
'links': self._get_links(request,
|
||||
volume['id']),
|
||||
},
|
||||
}
|
||||
|
||||
def detail(self, request, volume):
|
||||
"""Detailed view of a single volume."""
|
||||
return {
|
||||
'volume': {
|
||||
'id': volume.get('id'),
|
||||
'status': volume.get('status'),
|
||||
'size': volume.get('size'),
|
||||
'availability_zone': volume.get('availability_zone'),
|
||||
'created_at': volume.get('created_at'),
|
||||
'attachments': self._get_attachments(volume),
|
||||
'name': volume.get('display_name'),
|
||||
'description': volume.get('display_description'),
|
||||
'volume_type': self._get_volume_type(volume),
|
||||
'snapshot_id': volume.get('snapshot_id'),
|
||||
'source_volid': volume.get('source_volid'),
|
||||
'metadata': self._get_volume_metadata(volume),
|
||||
'links': self._get_links(request, volume['id'])
|
||||
}
|
||||
}
|
||||
|
||||
def _get_attachments(self, volume):
|
||||
"""Retrieves the attachments of the volume object"""
|
||||
attachments = []
|
||||
|
||||
if volume['attach_status'] == 'attached':
|
||||
d = {}
|
||||
volume_id = volume['id']
|
||||
|
||||
# note(justinsb): we use the volume id as the id of the attachments
|
||||
# object
|
||||
d['id'] = volume_id
|
||||
|
||||
d['volume_id'] = volume_id
|
||||
d['server_id'] = volume['instance_uuid']
|
||||
if volume.get('mountpoint'):
|
||||
d['device'] = volume['mountpoint']
|
||||
attachments.append(d)
|
||||
|
||||
return attachments
|
||||
|
||||
def _get_volume_metadata(self, volume):
|
||||
"""Retrieves the metadata of the volume object"""
|
||||
if volume.get('volume_metadata'):
|
||||
metadata = volume.get('volume_metadata')
|
||||
return dict((item['key'], item['value']) for item in metadata)
|
||||
# avoid circular ref when vol is a Volume instance
|
||||
elif volume.get('metadata') and isinstance(volume.get('metadata'),
|
||||
dict):
|
||||
return volume['metadata']
|
||||
return {}
|
||||
|
||||
def _get_volume_type(self, volume):
|
||||
"""Retrieves the type the volume object is"""
|
||||
if volume['volume_type_id'] and volume.get('volume_type'):
|
||||
return volume['volume_type']['name']
|
||||
else:
|
||||
return volume['volume_type_id']
|
||||
|
||||
def _list_view(self, func, request, volumes):
|
||||
"""Provide a view for a list of volumes."""
|
||||
volumes_list = [func(request, volume)['volume'] for volume in volumes]
|
||||
volumes_links = self._get_collection_links(request,
|
||||
volumes,
|
||||
self._collection_name)
|
||||
volumes_dict = dict(volumes=volumes_list)
|
||||
|
||||
if volumes_links:
|
||||
volumes_dict['volumes_links'] = volumes_links
|
||||
|
||||
return volumes_dict
|
@ -1,362 +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 manila.api import common
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.v2.views import volumes as volume_views
|
||||
from manila.api import xmlutil
|
||||
from manila import exception
|
||||
from manila import flags
|
||||
from manila.openstack.common import log as logging
|
||||
from manila.openstack.common import uuidutils
|
||||
from manila import utils
|
||||
from manila import volume
|
||||
from manila.volume import volume_types
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
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('name')
|
||||
elem.set('description')
|
||||
elem.set('volume_type')
|
||||
elem.set('snapshot_id')
|
||||
elem.set('source_volid')
|
||||
|
||||
attachments = xmlutil.SubTemplateElement(elem, 'attachments')
|
||||
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
|
||||
selector='attachments')
|
||||
make_attachment(attachment)
|
||||
|
||||
# Attach metadata node
|
||||
elem.append(common.MetadataTemplate())
|
||||
|
||||
|
||||
volume_nsmap = {None: xmlutil.XMLNS_VOLUME_V2, 'atom': xmlutil.XMLNS_ATOM}
|
||||
|
||||
|
||||
class VolumeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('volume', selector='volume')
|
||||
make_volume(root)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
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, nsmap=volume_nsmap)
|
||||
|
||||
|
||||
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 = ['name', '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 = utils.safe_minidom_parse_string(string)
|
||||
volume = self._extract_volume(dom)
|
||||
return {'body': {'volume': volume}}
|
||||
|
||||
|
||||
class VolumeController(wsgi.Controller):
|
||||
"""The Volumes API controller for the OpenStack API."""
|
||||
|
||||
_view_builder_class = volume_views.ViewBuilder
|
||||
|
||||
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['manila.context']
|
||||
|
||||
try:
|
||||
vol = self.volume_api.get(context, id)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
return self._view_builder.detail(req, vol)
|
||||
|
||||
def delete(self, req, id):
|
||||
"""Delete a volume."""
|
||||
context = req.environ['manila.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._get_volumes(req, is_detail=False)
|
||||
|
||||
@wsgi.serializers(xml=VolumesTemplate)
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of volumes."""
|
||||
return self._get_volumes(req, is_detail=True)
|
||||
|
||||
def _get_volumes(self, req, is_detail):
|
||||
"""Returns a list of volumes, transformed through view builder."""
|
||||
|
||||
context = req.environ['manila.context']
|
||||
|
||||
params = req.params.copy()
|
||||
marker = params.pop('marker', None)
|
||||
limit = params.pop('limit', None)
|
||||
sort_key = params.pop('sort_key', 'created_at')
|
||||
sort_dir = params.pop('sort_dir', 'desc')
|
||||
params.pop('offset', None)
|
||||
filters = params
|
||||
|
||||
remove_invalid_options(context,
|
||||
filters, self._get_volume_filter_options())
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in filters:
|
||||
filters['display_name'] = filters['name']
|
||||
del filters['name']
|
||||
|
||||
volumes = self.volume_api.get_all(context, marker, limit, sort_key,
|
||||
sort_dir, filters)
|
||||
limited_list = common.limited(volumes, req)
|
||||
|
||||
if is_detail:
|
||||
volumes = self._view_builder.detail_list(req, limited_list)
|
||||
else:
|
||||
volumes = self._view_builder.summary_list(req, limited_list)
|
||||
return volumes
|
||||
|
||||
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 uuidutils.is_uuid_like(image_uuid):
|
||||
msg = _("Invalid imageRef provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
return image_uuid
|
||||
|
||||
@wsgi.response(202)
|
||||
@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'):
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
context = req.environ['manila.context']
|
||||
volume = body['volume']
|
||||
|
||||
kwargs = {}
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if volume.get('name'):
|
||||
volume['display_name'] = volume.get('name')
|
||||
del volume['name']
|
||||
|
||||
# NOTE(thingee): v2 API allows description instead of description
|
||||
if volume.get('description'):
|
||||
volume['display_description'] = volume.get('description')
|
||||
del volume['description']
|
||||
|
||||
req_volume_type = volume.get('volume_type', None)
|
||||
if req_volume_type:
|
||||
try:
|
||||
kwargs['volume_type'] = volume_types.get_volume_type(
|
||||
context, req_volume_type)
|
||||
except exception.VolumeTypeNotFound:
|
||||
explanation = 'Volume type not found.'
|
||||
raise exc.HTTPNotFound(explanation=explanation)
|
||||
|
||||
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
|
||||
|
||||
source_volid = volume.get('source_volid')
|
||||
if source_volid is not None:
|
||||
kwargs['source_volume'] = self.volume_api.get_volume(context,
|
||||
source_volid)
|
||||
else:
|
||||
kwargs['source_volume'] = None
|
||||
|
||||
size = volume.get('size', None)
|
||||
if size is None and kwargs['snapshot'] is not None:
|
||||
size = kwargs['snapshot']['volume_size']
|
||||
elif size is None and kwargs['source_volume'] is not None:
|
||||
size = kwargs['source_volume']['size']
|
||||
|
||||
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 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 = self._view_builder.summary(req, dict(new_volume.iteritems()))
|
||||
|
||||
return retval
|
||||
|
||||
def _get_volume_filter_options(self):
|
||||
"""Return volume search options allowed by non-admin."""
|
||||
return ('name', 'status')
|
||||
|
||||
@wsgi.serializers(xml=VolumeTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Update a volume."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
if not body:
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
if 'volume' not in body:
|
||||
raise exc.HTTPBadRequest()
|
||||
|
||||
volume = body['volume']
|
||||
update_dict = {}
|
||||
|
||||
valid_update_keys = (
|
||||
'name',
|
||||
'description',
|
||||
'metadata',
|
||||
)
|
||||
|
||||
for key in valid_update_keys:
|
||||
if key in volume:
|
||||
update_dict[key] = volume[key]
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'name' in update_dict:
|
||||
update_dict['display_name'] = update_dict['name']
|
||||
del update_dict['name']
|
||||
|
||||
# NOTE(thingee): v2 API allows name instead of display_name
|
||||
if 'description' in update_dict:
|
||||
update_dict['display_description'] = update_dict['description']
|
||||
del update_dict['description']
|
||||
|
||||
try:
|
||||
volume = self.volume_api.get(context, id)
|
||||
self.volume_api.update(context, volume, update_dict)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
volume.update(update_dict)
|
||||
|
||||
return self._view_builder.detail(req, volume)
|
||||
|
||||
|
||||
def create_resource(ext_mgr):
|
||||
return wsgi.Resource(VolumeController(ext_mgr))
|
||||
|
||||
|
||||
def remove_invalid_options(context, filters, 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 filters
|
||||
if opt not in allowed_search_options]
|
||||
bad_options = ", ".join(unknown_options)
|
||||
log_msg = _("Removing options '%s' from query") % bad_options
|
||||
LOG.debug(log_msg)
|
||||
for opt in unknown_options:
|
||||
del filters[opt]
|
@ -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 manila.api 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)
|
Loading…
x
Reference in New Issue
Block a user