From b814f9fef3efa1bdcb7e03a9161e08721b7bc8c4 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Fri, 15 Jul 2011 17:56:27 -0700 Subject: VSA: first cut. merged with 1279 --- nova/api/ec2/__init__.py | 4 + nova/api/ec2/cloud.py | 164 +++++++- nova/api/openstack/contrib/drive_types.py | 147 +++++++ .../openstack/contrib/virtual_storage_arrays.py | 454 +++++++++++++++++++++ nova/api/openstack/contrib/volumes.py | 14 +- 5 files changed, 775 insertions(+), 8 deletions(-) create mode 100644 nova/api/openstack/contrib/drive_types.py create mode 100644 nova/api/openstack/contrib/virtual_storage_arrays.py (limited to 'nova/api') 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} -- cgit