summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorvladimir.p <vladimir@zadarastorage.com>2011-07-15 17:56:27 -0700
committervladimir.p <vladimir@zadarastorage.com>2011-07-15 17:56:27 -0700
commitb814f9fef3efa1bdcb7e03a9161e08721b7bc8c4 (patch)
tree71f9fc135a3de3b4d2938a365e1a080f2d5bfb39 /nova/api
parent3a11738f517999ed1fd3a2c0a7ca452c7191b50f (diff)
VSA: first cut. merged with 1279
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/__init__.py4
-rw-r--r--nova/api/ec2/cloud.py164
-rw-r--r--nova/api/openstack/contrib/drive_types.py147
-rw-r--r--nova/api/openstack/contrib/virtual_storage_arrays.py454
-rw-r--r--nova/api/openstack/contrib/volumes.py14
5 files changed, 775 insertions, 8 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 890d57fe7..ec44c02ef 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -269,6 +269,10 @@ class Authorizer(wsgi.Middleware):
'DescribeImageAttribute': ['all'],
'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
'UpdateImage': ['projectmanager', 'sysadmin'],
+ 'CreateVsa': ['projectmanager', 'sysadmin'],
+ 'DeleteVsa': ['projectmanager', 'sysadmin'],
+ 'DescribeVsas': ['projectmanager', 'sysadmin'],
+ 'DescribeDriveTypes': ['projectmanager', 'sysadmin'],
},
'AdminController': {
# All actions have the same permission: ['none'] (the default)
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index acfd1361c..786ceaccc 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -42,6 +42,8 @@ from nova import network
from nova import rpc
from nova import utils
from nova import volume
+from nova import vsa
+from nova.vsa import drive_types
from nova.api.ec2 import ec2utils
from nova.compute import instance_types
from nova.image import s3
@@ -87,6 +89,7 @@ class CloudController(object):
self.compute_api = compute.API(
network_api=self.network_api,
volume_api=self.volume_api)
+ self.vsa_api = vsa.API(compute_api=self.compute_api)
self.setup()
def __str__(self):
@@ -727,12 +730,26 @@ class CloudController(object):
snapshot_id = None
LOG.audit(_("Create volume of %s GB"), size, context=context)
+ to_vsa_id = kwargs.get('to_vsa_id', None)
+ if to_vsa_id:
+ to_vsa_id = ec2utils.ec2_id_to_id(to_vsa_id)
+
+ from_vsa_id = kwargs.get('from_vsa_id', None)
+ if from_vsa_id:
+ from_vsa_id = ec2utils.ec2_id_to_id(from_vsa_id)
+
+ if to_vsa_id or from_vsa_id:
+ LOG.audit(_("Create volume of %s GB associated with VSA "\
+ "(to: %d, from: %d)"),
+ size, to_vsa_id, from_vsa_id, context=context)
+
volume = self.volume_api.create(
context,
size=size,
snapshot_id=snapshot_id,
name=kwargs.get('display_name'),
- description=kwargs.get('display_description'))
+ description=kwargs.get('display_description'),
+ to_vsa_id=to_vsa_id, from_vsa_id=from_vsa_id)
# 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.
@@ -786,6 +803,151 @@ class CloudController(object):
'status': volume['attach_status'],
'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')}
+ def _format_vsa(self, context, p_vsa):
+ vsa = {}
+ vsa['vsaId'] = p_vsa['id']
+ vsa['status'] = p_vsa['status']
+ vsa['availabilityZone'] = p_vsa['availability_zone']
+ vsa['createTime'] = p_vsa['created_at']
+ vsa['name'] = p_vsa['name']
+ vsa['displayName'] = p_vsa['display_name']
+ vsa['displayDescription'] = p_vsa['display_description']
+ vsa['vcCount'] = p_vsa['vc_count']
+ if p_vsa['vsa_instance_type']:
+ vsa['vcType'] = p_vsa['vsa_instance_type'].get('name', None)
+ else:
+ vsa['vcType'] = None
+ return vsa
+
+ def create_vsa(self, context, **kwargs):
+ display_name = kwargs.get('display_name')
+ display_description = kwargs.get('display_description')
+ vc_count = int(kwargs.get('vc_count', 1))
+ instance_type = instance_types.get_instance_type_by_name(
+ kwargs.get('vc_type', FLAGS.default_vsa_instance_type))
+ image_name = kwargs.get('image_name')
+ availability_zone = kwargs.get('placement', {}).get(
+ 'AvailabilityZone')
+ #storage = ast.literal_eval(kwargs.get('storage', '[]'))
+ storage = kwargs.get('storage', [])
+ shared = kwargs.get('shared', False)
+
+ vc_type = instance_type['name']
+ _storage = str(storage)
+ LOG.audit(_("Create VSA %(display_name)s vc_count:%(vc_count)d "\
+ "vc_type:%(vc_type)s storage:%(_storage)s"), locals())
+
+ vsa = self.vsa_api.create(context, display_name, display_description,
+ vc_count, instance_type, image_name,
+ availability_zone, storage, shared)
+ return {'vsaSet': [self._format_vsa(context, vsa)]}
+
+ def update_vsa(self, context, vsa_id, **kwargs):
+ LOG.audit(_("Update VSA %s"), vsa_id)
+ updatable_fields = ['display_name', 'display_description', 'vc_count']
+ changes = {}
+ for field in updatable_fields:
+ if field in kwargs:
+ changes[field] = kwargs[field]
+ if changes:
+ vsa_id = ec2utils.ec2_id_to_id(vsa_id)
+ self.vsa_api.update(context, vsa_id=vsa_id, **changes)
+ return True
+
+ def delete_vsa(self, context, vsa_id, **kwargs):
+ LOG.audit(_("Delete VSA %s"), vsa_id)
+ vsa_id = ec2utils.ec2_id_to_id(vsa_id)
+
+ self.vsa_api.delete(context, vsa_id)
+
+ return True
+
+ def describe_vsas(self, context, vsa_id=None, status=None,
+ availability_zone=None, **kwargs):
+# LOG.debug(_("vsa_id=%s, status=%s, az=%s"),
+# (vsa_id, status, availability_zone))
+ result = []
+ vsas = []
+ if vsa_id is not None:
+ for ec2_id in vsa_id:
+ internal_id = ec2utils.ec2_id_to_id(ec2_id)
+ vsa = self.vsa_api.get(context, internal_id)
+ vsas.append(vsa)
+ else:
+ vsas = self.vsa_api.get_all(context)
+
+ if status:
+ result = []
+ for vsa in vsas:
+ if vsa['status'] in status:
+ result.append(vsa)
+ vsas = result
+
+ if availability_zone:
+ result = []
+ for vsa in vsas:
+ if vsa['availability_zone'] in availability_zone:
+ result.append(vsa)
+ vsas = result
+
+ return {'vsaSet': [self._format_vsa(context, vsa) for vsa in vsas]}
+
+ def create_drive_type(self, context, **kwargs):
+ name = kwargs.get('name')
+ type = kwargs.get('type')
+ size_gb = int(kwargs.get('size_gb'))
+ rpm = kwargs.get('rpm')
+ capabilities = kwargs.get('capabilities')
+ visible = kwargs.get('visible', True)
+
+ LOG.audit(_("Create Drive Type %(name)s: %(type)s %(size_gb)d "\
+ "%(rpm)s %(capabilities)s %(visible)s"),
+ locals())
+
+ rv = drive_types.drive_type_create(context, type, size_gb, rpm,
+ capabilities, visible, name)
+ return {'driveTypeSet': [dict(rv)]}
+
+ def update_drive_type(self, context, name, **kwargs):
+ LOG.audit(_("Update Drive Type %s"), name)
+ updatable_fields = ['type',
+ 'size_gb',
+ 'rpm',
+ 'capabilities',
+ 'visible']
+ changes = {}
+ for field in updatable_fields:
+ if field in kwargs and \
+ kwargs[field] is not None and \
+ kwargs[field] != '':
+ changes[field] = kwargs[field]
+ if changes:
+ drive_types.drive_type_update(context, name, **changes)
+ return True
+
+ def rename_drive_type(self, context, name, new_name):
+ drive_types.drive_type_rename(context, name, new_name)
+ return True
+
+ def delete_drive_type(self, context, name):
+ drive_types.drive_type_delete(context, name)
+ return True
+
+ def describe_drive_types(self, context, names=None, visible=True):
+
+ drives = []
+ if names is not None:
+ for name in names:
+ drive = drive_types.drive_type_get_by_name(context, name)
+ if drive['visible'] == visible:
+ drives.append(drive)
+ else:
+ drives = drive_types.drive_type_get_all(context, visible)
+
+ # VP-TODO: Change it later to EC2 compatible func (output)
+
+ return {'driveTypeSet': [dict(drive) for drive in drives]}
+
def _convert_to_set(self, lst, label):
if lst is None or lst == []:
return None
diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py
new file mode 100644
index 000000000..85b3170cb
--- /dev/null
+++ b/nova/api/openstack/contrib/drive_types.py
@@ -0,0 +1,147 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage 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.
+
+""" The Drive Types extension for Virtual Storage Arrays"""
+
+
+from webob import exc
+
+from nova.vsa import drive_types
+from nova import db
+from nova import quota
+from nova import log as logging
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova.api.openstack import faults
+from nova.api.openstack import wsgi
+
+LOG = logging.getLogger("nova.api.drive_types")
+
+
+class DriveTypeController(object):
+ """The Drive Type API controller for the OpenStack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "drive_type": [
+ "id",
+ "displayName",
+ "type",
+ "size",
+ "rpm",
+ "capabilities",
+ ]}}}
+
+ def _drive_type_view(self, context, drive):
+ """Maps keys for drive types view."""
+ d = {}
+
+ d['id'] = drive['id']
+ d['displayName'] = drive['name']
+ d['type'] = drive['type']
+ d['size'] = drive['size_gb']
+ d['rpm'] = drive['rpm']
+ d['capabilities'] = drive['capabilities']
+ return d
+
+ def index(self, req):
+ """Returns a list of drive types."""
+
+ context = req.environ['nova.context']
+ drive_types = drive_types.drive_type_get_all(context)
+ limited_list = common.limited(drive_types, req)
+ res = [self._drive_type_view(context, drive) for drive in limited_list]
+ return {'drive_types': res}
+
+ def show(self, req, id):
+ """Return data about the given drive type."""
+ context = req.environ['nova.context']
+
+ try:
+ drive = drive_types.drive_type_get(context, id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ return {'drive_type': self._drive_type_view(context, drive)}
+
+ def create(self, req, body):
+ """Creates a new drive type."""
+ context = req.environ['nova.context']
+
+ if not body:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ drive = body['drive_type']
+
+ name = drive.get('displayName')
+ type = drive.get('type')
+ size = drive.get('size')
+ rpm = drive.get('rpm')
+ capabilities = drive.get('capabilities')
+
+ LOG.audit(_("Create drive type %(name)s for "\
+ "%(type)s:%(size)s:%(rpm)s"), locals(), context=context)
+
+ new_drive = drive_types.drive_type_create(context,
+ type=type,
+ size_gb=size,
+ rpm=rpm,
+ capabilities=capabilities,
+ name=name)
+
+ return {'drive_type': self._drive_type_view(context, new_drive)}
+
+ def delete(self, req, id):
+ """Deletes a drive type."""
+ context = req.environ['nova.context']
+
+ LOG.audit(_("Delete drive type with id: %s"), id, context=context)
+
+ try:
+ drive = drive_types.drive_type_get(context, id)
+ drive_types.drive_type_delete(context, drive['name'])
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+
+class Drive_types(extensions.ExtensionDescriptor):
+
+ def get_name(self):
+ return "DriveTypes"
+
+ def get_alias(self):
+ return "zadr-drive_types"
+
+ def get_description(self):
+ return "Drive Types support"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/drive_types/api/v1.1"
+
+ def get_updated(self):
+ return "2011-06-29T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+ res = extensions.ResourceExtension(
+ 'zadr-drive_types',
+ DriveTypeController())
+
+ resources.append(res)
+ return resources
diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py
new file mode 100644
index 000000000..eca2d68dd
--- /dev/null
+++ b/nova/api/openstack/contrib/virtual_storage_arrays.py
@@ -0,0 +1,454 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage 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.
+
+""" The virtul storage array extension"""
+
+
+from webob import exc
+
+from nova import vsa
+from nova import volume
+from nova import db
+from nova import quota
+from nova import exception
+from nova import log as logging
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova.api.openstack import faults
+from nova.api.openstack import wsgi
+from nova.api.openstack.contrib import volumes
+from nova.compute import instance_types
+
+from nova import flags
+FLAGS = flags.FLAGS
+
+LOG = logging.getLogger("nova.api.vsa")
+
+
+class VsaController(object):
+ """The Virtual Storage Array API controller for the OpenStack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "vsa": [
+ "id",
+ "name",
+ "displayName",
+ "displayDescription",
+ "createTime",
+ "status",
+ "vcType",
+ "vcCount",
+ "driveCount",
+ ]}}}
+
+ def __init__(self):
+ self.vsa_api = vsa.API()
+ super(VsaController, self).__init__()
+
+ def _vsa_view(self, context, vsa, details=False):
+ """Map keys for vsa summary/detailed view."""
+ d = {}
+
+ d['id'] = vsa['id']
+ d['name'] = vsa['name']
+ d['displayName'] = vsa['display_name']
+ d['displayDescription'] = vsa['display_description']
+
+ d['createTime'] = vsa['created_at']
+ d['status'] = vsa['status']
+
+ if vsa['vsa_instance_type']:
+ d['vcType'] = vsa['vsa_instance_type'].get('name', None)
+ else:
+ d['vcType'] = None
+
+ d['vcCount'] = vsa['vc_count']
+ d['driveCount'] = vsa['vol_count']
+
+ return d
+
+ def _items(self, req, details):
+ """Return summary or detailed list of VSAs."""
+ context = req.environ['nova.context']
+ vsas = self.vsa_api.get_all(context)
+ limited_list = common.limited(vsas, req)
+ res = [self._vsa_view(context, vsa, details) for vsa in limited_list]
+ return {'vsaSet': res}
+
+ def index(self, req):
+ """Return a short list of VSAs."""
+ return self._items(req, details=False)
+
+ def detail(self, req):
+ """Return a detailed list of VSAs."""
+ return self._items(req, details=True)
+
+ def show(self, req, id):
+ """Return data about the given VSA."""
+ context = req.environ['nova.context']
+
+ try:
+ vsa = self.vsa_api.get(context, vsa_id=id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ return {'vsa': self._vsa_view(context, vsa, details=True)}
+
+ def create(self, req, body):
+ """Create a new VSA."""
+ context = req.environ['nova.context']
+
+ if not body:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ vsa = body['vsa']
+
+ display_name = vsa.get('displayName')
+ display_description = vsa.get('displayDescription')
+ storage = vsa.get('storage')
+ shared = vsa.get('shared')
+ vc_type = vsa.get('vcType', FLAGS.default_vsa_instance_type)
+ availability_zone = vsa.get('placement', {}).get('AvailabilityZone')
+
+ try:
+ instance_type = instance_types.get_instance_type_by_name(vc_type)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ LOG.audit(_("Create VSA %(display_name)s of type %(vc_type)s"),
+ locals(), context=context)
+
+ result = self.vsa_api.create(context,
+ display_name=display_name,
+ display_description=display_description,
+ storage=storage,
+ shared=shared,
+ instance_type=instance_type,
+ availability_zone=availability_zone)
+
+ return {'vsa': self._vsa_view(context, result, details=True)}
+
+ def delete(self, req, id):
+ """Delete a VSA."""
+ context = req.environ['nova.context']
+
+ LOG.audit(_("Delete VSA with id: %s"), id, context=context)
+
+ try:
+ self.vsa_api.delete(context, vsa_id=id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+
+class VsaVolumeDriveController(volumes.VolumeController):
+ """The base class for VSA volumes & drives.
+
+ A child resource of the VSA object. Allows operations with
+ volumes and drives created to/from particular VSA
+
+ """
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "volume": [
+ "id",
+ "name",
+ "status",
+ "size",
+ "availabilityZone",
+ "createdAt",
+ "displayName",
+ "displayDescription",
+ "vsaId",
+ ]}}}
+
+ def __init__(self):
+ # self.compute_api = compute.API()
+ # self.vsa_api = vsa.API()
+ self.volume_api = volume.API()
+ super(VsaVolumeDriveController, self).__init__()
+
+ def _translation(self, context, vol, vsa_id, details):
+ if details:
+ translation = volumes.translate_volume_detail_view
+ else:
+ translation = volumes.translate_volume_summary_view
+
+ d = translation(context, vol)
+ d['vsaId'] = vol[self.direction]
+ return d
+
+ def _check_volume_ownership(self, context, vsa_id, id):
+ obj = self.object
+ try:
+ volume_ref = self.volume_api.get(context, volume_id=id)
+ except exception.NotFound:
+ LOG.error(_("%(obj)s with ID %(id)s not found"), locals())
+ raise
+
+ own_vsa_id = volume_ref[self.direction]
+ if own_vsa_id != int(vsa_id):
+ LOG.error(_("%(obj)s with ID %(id)s belongs to VSA %(own_vsa_id)s"\
+ " and not to VSA %(vsa_id)s."), locals())
+ raise exception.Invalid()
+
+ def _items(self, req, vsa_id, details):
+ """Return summary or detailed list of volumes for particular VSA."""
+ context = req.environ['nova.context']
+
+ vols = self.volume_api.get_all_by_vsa(context, vsa_id,
+ self.direction.split('_')[0])
+ limited_list = common.limited(vols, req)
+
+ res = [self._translation(context, vol, vsa_id, details) \
+ for vol in limited_list]
+
+ return {self.objects: res}
+
+ def index(self, req, vsa_id):
+ """Return a short list of volumes created from particular VSA."""
+ LOG.audit(_("Index. vsa_id=%(vsa_id)s"), locals())
+ return self._items(req, vsa_id, details=False)
+
+ def detail(self, req, vsa_id):
+ """Return a detailed list of volumes created from particular VSA."""
+ LOG.audit(_("Detail. vsa_id=%(vsa_id)s"), locals())
+ return self._items(req, vsa_id, details=True)
+
+ def create(self, req, vsa_id, body):
+ """Create a new volume from VSA."""
+ LOG.audit(_("Create. vsa_id=%(vsa_id)s, body=%(body)s"), locals())
+ context = req.environ['nova.context']
+
+ if not body:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ vol = body[self.object]
+ size = vol['size']
+ LOG.audit(_("Create volume of %(size)s GB from VSA ID %(vsa_id)s"),
+ locals(), context=context)
+
+ new_volume = self.volume_api.create(context, size, None,
+ vol.get('displayName'),
+ vol.get('displayDescription'),
+ from_vsa_id=vsa_id)
+
+ return {self.object: self._translation(context, new_volume,
+ vsa_id, True)}
+
+ def update(self, req, vsa_id, id, body):
+ """Update a volume."""
+ context = req.environ['nova.context']
+
+ try:
+ self._check_volume_ownership(context, vsa_id, id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ except exception.Invalid:
+ return faults.Fault(exc.HTTPBadRequest())
+
+ vol = body[self.object]
+ updatable_fields = ['display_name',
+ 'display_description',
+ 'status',
+ 'provider_location',
+ 'provider_auth']
+ changes = {}
+ for field in updatable_fields:
+ if field in vol:
+ changes[field] = vol[field]
+
+ obj = self.object
+ LOG.audit(_("Update %(obj)s with id: %(id)s, changes: %(changes)s"),
+ locals(), context=context)
+
+ try:
+ self.volume_api.update(context, volume_id=id, fields=changes)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+ def delete(self, req, vsa_id, id):
+ """Delete a volume."""
+ context = req.environ['nova.context']
+
+ LOG.audit(_("Delete. vsa_id=%(vsa_id)s, id=%(id)s"), locals())
+
+ try:
+ self._check_volume_ownership(context, vsa_id, id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ except exception.Invalid:
+ return faults.Fault(exc.HTTPBadRequest())
+
+ return super(VsaVolumeDriveController, self).delete(req, id)
+
+ def show(self, req, vsa_id, id):
+ """Return data about the given volume."""
+ context = req.environ['nova.context']
+
+ LOG.audit(_("Show. vsa_id=%(vsa_id)s, id=%(id)s"), locals())
+
+ try:
+ self._check_volume_ownership(context, vsa_id, id)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ except exception.Invalid:
+ return faults.Fault(exc.HTTPBadRequest())
+
+ return super(VsaVolumeDriveController, self).show(req, id)
+
+
+class VsaVolumeController(VsaVolumeDriveController):
+ """The VSA volume API controller for the Openstack API.
+
+ A child resource of the VSA object. Allows operations with volumes created
+ by particular VSA
+
+ """
+
+ def __init__(self):
+ self.direction = 'from_vsa_id'
+ self.objects = 'volumes'
+ self.object = 'volume'
+ super(VsaVolumeController, self).__init__()
+
+
+class VsaDriveController(VsaVolumeDriveController):
+ """The VSA Drive API controller for the Openstack API.
+
+ A child resource of the VSA object. Allows operations with drives created
+ for particular VSA
+
+ """
+
+ def __init__(self):
+ self.direction = 'to_vsa_id'
+ self.objects = 'drives'
+ self.object = 'drive'
+ super(VsaDriveController, self).__init__()
+
+ def create(self, req, vsa_id, body):
+ """Create a new drive for VSA. Should be done through VSA APIs"""
+ return faults.Fault(exc.HTTPBadRequest())
+
+ def update(self, req, vsa_id, id, body):
+ """Update a drive. Should be done through VSA APIs"""
+ return faults.Fault(exc.HTTPBadRequest())
+
+
+class VsaVPoolController(object):
+ """The vPool VSA API controller for the OpenStack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "vpool": [
+ "id",
+ "vsaId",
+ "name",
+ "displayName",
+ "displayDescription",
+ "driveCount",
+ "driveIds",
+ "protection",
+ "stripeSize",
+ "stripeWidth",
+ "createTime",
+ "status",
+ ]}}}
+
+ def __init__(self):
+ self.vsa_api = vsa.API()
+ super(VsaVPoolController, self).__init__()
+
+ def index(self, req, vsa_id):
+ """Return a short list of vpools created from particular VSA."""
+ return {'vpools': []}
+
+ def create(self, req, vsa_id, body):
+ """Create a new vPool for VSA."""
+ return faults.Fault(exc.HTTPBadRequest())
+
+ def update(self, req, vsa_id, id, body):
+ """Update vPool parameters."""
+ return faults.Fault(exc.HTTPBadRequest())
+
+ def delete(self, req, vsa_id, id):
+ """Delete a vPool."""
+ return faults.Fault(exc.HTTPBadRequest())
+
+ def show(self, req, vsa_id, id):
+ """Return data about the given vPool."""
+ return faults.Fault(exc.HTTPBadRequest())
+
+
+class Virtual_storage_arrays(extensions.ExtensionDescriptor):
+
+ def get_name(self):
+ return "VSAs"
+
+ def get_alias(self):
+ return "zadr-vsa"
+
+ def get_description(self):
+ return "Virtual Storage Arrays support"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/vsa/api/v1.1"
+
+ def get_updated(self):
+ return "2011-06-29T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+ res = extensions.ResourceExtension(
+ 'zadr-vsa',
+ VsaController(),
+ collection_actions={'detail': 'GET'},
+ member_actions={'add_capacity': 'POST',
+ 'remove_capacity': 'POST'})
+ resources.append(res)
+
+ res = extensions.ResourceExtension('volumes',
+ VsaVolumeController(),
+ collection_actions={'detail': 'GET'},
+ parent=dict(
+ member_name='vsa',
+ collection_name='zadr-vsa'))
+ resources.append(res)
+
+ res = extensions.ResourceExtension('drives',
+ VsaDriveController(),
+ collection_actions={'detail': 'GET'},
+ parent=dict(
+ member_name='vsa',
+ collection_name='zadr-vsa'))
+ resources.append(res)
+
+ res = extensions.ResourceExtension('vpools',
+ VsaVPoolController(),
+ parent=dict(
+ member_name='vsa',
+ collection_name='zadr-vsa'))
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index e5e2c5b50..3c3d40c0f 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -33,17 +33,17 @@ LOG = logging.getLogger("nova.api.volumes")
FLAGS = flags.FLAGS
-def _translate_volume_detail_view(context, vol):
+def translate_volume_detail_view(context, vol):
"""Maps keys for volumes details view."""
- d = _translate_volume_summary_view(context, vol)
+ d = translate_volume_summary_view(context, vol)
# No additional data / lookups at the moment
return d
-def _translate_volume_summary_view(context, vol):
+def translate_volume_summary_view(context, vol):
"""Maps keys for volumes summary view."""
d = {}
@@ -92,7 +92,7 @@ class VolumeController(object):
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
- return {'volume': _translate_volume_detail_view(context, vol)}
+ return {'volume': translate_volume_detail_view(context, vol)}
def delete(self, req, id):
"""Delete a volume."""
@@ -108,11 +108,11 @@ class VolumeController(object):
def index(self, req):
"""Returns a summary list of volumes."""
- return self._items(req, entity_maker=_translate_volume_summary_view)
+ return self._items(req, entity_maker=translate_volume_summary_view)
def detail(self, req):
"""Returns a detailed list of volumes."""
- return self._items(req, entity_maker=_translate_volume_detail_view)
+ 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."""
@@ -140,7 +140,7 @@ class VolumeController(object):
# Work around problem that instance is lazy-loaded...
new_volume['instance'] = None
- retval = _translate_volume_detail_view(context, new_volume)
+ retval = translate_volume_detail_view(context, new_volume)
return {'volume': retval}