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 From 15bbaf8bbdd48231f9ce98e4d8867b0477b44645 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Mon, 18 Jul 2011 09:57:31 -0700 Subject: localization changes. Removed vsa params from volume cloud API. Alex changes --- nova/api/ec2/cloud.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 786ceaccc..e31b755de 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -730,26 +730,12 @@ 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'), - to_vsa_id=to_vsa_id, from_vsa_id=from_vsa_id) + description=kwargs.get('display_description')) # 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. @@ -864,8 +850,7 @@ class CloudController(object): 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)) + LOG.audit(_("Describe VSAs")) result = [] vsas = [] if vsa_id is not None: -- cgit From 9e74803d5eb8a70ba829ac0569f1cd6cd372a6f2 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Fri, 22 Jul 2011 15:14:29 -0700 Subject: Reverted volume driver part --- nova/api/ec2/cloud.py | 19 ++++-- nova/api/openstack/contrib/drive_types.py | 55 ++++++++-------- .../openstack/contrib/virtual_storage_arrays.py | 77 +++++++++++----------- 3 files changed, 78 insertions(+), 73 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e31b755de..7d0ce360f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -889,12 +889,15 @@ class CloudController(object): "%(rpm)s %(capabilities)s %(visible)s"), locals()) - rv = drive_types.drive_type_create(context, type, size_gb, rpm, - capabilities, visible, name) + rv = drive_types.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) + + dtype = drive_types.get_by_name(context, name) + updatable_fields = ['type', 'size_gb', 'rpm', @@ -906,16 +909,18 @@ class CloudController(object): kwargs[field] is not None and \ kwargs[field] != '': changes[field] = kwargs[field] + if changes: - drive_types.drive_type_update(context, name, **changes) + drive_types.update(context, dtype['id'], **changes) return True def rename_drive_type(self, context, name, new_name): - drive_types.drive_type_rename(context, name, new_name) + drive_types.rename(context, name, new_name) return True def delete_drive_type(self, context, name): - drive_types.drive_type_delete(context, name) + dtype = drive_types.get_by_name(context, name) + drive_types.delete(context, dtype['id']) return True def describe_drive_types(self, context, names=None, visible=True): @@ -923,11 +928,11 @@ class CloudController(object): drives = [] if names is not None: for name in names: - drive = drive_types.drive_type_get_by_name(context, name) + drive = drive_types.get_by_name(context, name) if drive['visible'] == visible: drives.append(drive) else: - drives = drive_types.drive_type_get_all(context, visible) + drives = drive_types.get_all(context, visible) # VP-TODO: Change it later to EC2 compatible func (output) diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py index 85b3170cb..590eaaec0 100644 --- a/nova/api/openstack/contrib/drive_types.py +++ b/nova/api/openstack/contrib/drive_types.py @@ -21,6 +21,7 @@ from webob import exc from nova.vsa import drive_types +from nova import exception from nova import db from nova import quota from nova import log as logging @@ -32,6 +33,19 @@ from nova.api.openstack import wsgi LOG = logging.getLogger("nova.api.drive_types") +def _drive_type_view(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 + + class DriveTypeController(object): """The Drive Type API controller for the OpenStack API.""" @@ -47,25 +61,13 @@ class DriveTypeController(object): "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] + dtypes = drive_types.get_all(context) + limited_list = common.limited(dtypes, req) + res = [_drive_type_view(drive) for drive in limited_list] return {'drive_types': res} def show(self, req, id): @@ -73,11 +75,11 @@ class DriveTypeController(object): context = req.environ['nova.context'] try: - drive = drive_types.drive_type_get(context, id) + drive = drive_types.get(context, id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'drive_type': self._drive_type_view(context, drive)} + return {'drive_type': _drive_type_view(drive)} def create(self, req, body): """Creates a new drive type.""" @@ -97,14 +99,14 @@ class DriveTypeController(object): 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) + new_drive = drive_types.create(context, + type=type, + size_gb=size, + rpm=rpm, + capabilities=capabilities, + name=name) - return {'drive_type': self._drive_type_view(context, new_drive)} + return {'drive_type': _drive_type_view(new_drive)} def delete(self, req, id): """Deletes a drive type.""" @@ -113,11 +115,10 @@ class DriveTypeController(object): 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']) + drive_types.delete(context, id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + # return exc.HTTPAccepted() class Drive_types(extensions.ExtensionDescriptor): diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index eca2d68dd..3c1362f0c 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -39,6 +39,29 @@ FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.vsa") +def _vsa_view(context, vsa, details=False): + """Map keys for vsa summary/detailed view.""" + d = {} + + d['id'] = vsa.get('id') + d['name'] = vsa.get('name') + d['displayName'] = vsa.get('display_name') + d['displayDescription'] = vsa.get('display_description') + + d['createTime'] = vsa.get('created_at') + d['status'] = vsa.get('status') + + if 'vsa_instance_type' in vsa: + d['vcType'] = vsa['vsa_instance_type'].get('name', None) + else: + d['vcType'] = None + + d['vcCount'] = vsa.get('vc_count') + d['driveCount'] = vsa.get('vol_count') + + return d + + class VsaController(object): """The Virtual Storage Array API controller for the OpenStack API.""" @@ -61,34 +84,12 @@ class VsaController(object): 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] + res = [_vsa_view(context, vsa, details) for vsa in limited_list] return {'vsaSet': res} def index(self, req): @@ -108,24 +109,20 @@ class VsaController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'vsa': self._vsa_view(context, vsa, details=True)} + return {'vsa': _vsa_view(context, vsa, details=True)} def create(self, req, body): """Create a new VSA.""" context = req.environ['nova.context'] - if not body: + if not body or 'vsa' not in body: + LOG.debug(_("No body provided"), context=context) 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: @@ -134,15 +131,17 @@ class VsaController(object): 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) + args = dict(display_name=display_name, + display_description=vsa.get('displayDescription'), + instance_type=instance_type, + storage=vsa.get('storage'), + shared=vsa.get('shared'), + availability_zone=vsa.get('placement', {}).\ + get('AvailabilityZone')) - return {'vsa': self._vsa_view(context, result, details=True)} + result = self.vsa_api.create(context, **args) + + return {'vsa': _vsa_view(context, result, details=True)} def delete(self, req, id): """Delete a VSA.""" @@ -154,7 +153,7 @@ class VsaController(object): self.vsa_api.delete(context, vsa_id=id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + # return exc.HTTPAccepted() class VsaVolumeDriveController(volumes.VolumeController): -- cgit From 61781dae931ced36db0f2735da474d0bd38a53cf Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Fri, 22 Jul 2011 20:25:32 -0700 Subject: more unittest changes --- nova/api/openstack/contrib/virtual_storage_arrays.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index 3c1362f0c..6139b494e 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -353,6 +353,10 @@ class VsaDriveController(VsaVolumeDriveController): """Update a drive. Should be done through VSA APIs""" return faults.Fault(exc.HTTPBadRequest()) + def delete(self, req, vsa_id, id): + """Delete a volume. Should be done through VSA APIs""" + return faults.Fault(exc.HTTPBadRequest()) + class VsaVPoolController(object): """The vPool VSA API controller for the OpenStack API.""" -- cgit From c500eac4589e9cb22e5e71b900164a151290ec03 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Mon, 25 Jul 2011 16:26:23 -0700 Subject: some cleanup. VSA flag status changes. returned some files --- nova/api/ec2/cloud.py | 1 - nova/api/openstack/contrib/drive_types.py | 2 +- nova/api/openstack/contrib/virtual_storage_arrays.py | 19 +++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 56a5850f6..6fc74c92a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -902,7 +902,6 @@ class CloudController(object): 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) diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py index 590eaaec0..6454fd81f 100644 --- a/nova/api/openstack/contrib/drive_types.py +++ b/nova/api/openstack/contrib/drive_types.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 Zadara Storage Inc. +# Copyright (c) 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -118,7 +119,6 @@ class DriveTypeController(object): drive_types.delete(context, id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - # return exc.HTTPAccepted() class Drive_types(extensions.ExtensionDescriptor): diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index 6139b494e..68a00fd7d 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 Zadara Storage Inc. +# Copyright (c) 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -153,7 +154,6 @@ class VsaController(object): self.vsa_api.delete(context, vsa_id=id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - # return exc.HTTPAccepted() class VsaVolumeDriveController(volumes.VolumeController): @@ -193,6 +193,7 @@ class VsaVolumeDriveController(volumes.VolumeController): d = translation(context, vol) d['vsaId'] = vol[self.direction] + d['name'] = vol['name'] return d def _check_volume_ownership(self, context, vsa_id, id): @@ -265,15 +266,17 @@ class VsaVolumeDriveController(volumes.VolumeController): return faults.Fault(exc.HTTPBadRequest()) vol = body[self.object] - updatable_fields = ['display_name', - 'display_description', - 'status', - 'provider_location', - 'provider_auth'] + updatable_fields = [{'displayName': 'display_name'}, + {'displayDescription': 'display_description'}, + {'status': 'status'}, + {'providerLocation': 'provider_location'}, + {'providerAuth': 'provider_auth'}] changes = {} for field in updatable_fields: - if field in vol: - changes[field] = vol[field] + key = field.keys()[0] + val = field[key] + if key in vol: + changes[val] = vol[key] obj = self.object LOG.audit(_("Update %(obj)s with id: %(id)s, changes: %(changes)s"), -- cgit From b4159d95c32382d124c3f3f0a49f8ad9f2d41036 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 28 Jul 2011 00:27:16 -0700 Subject: some minor cosmetic work. addressed some dead code section --- nova/api/openstack/contrib/drive_types.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py index 6454fd81f..f2cbd3715 100644 --- a/nova/api/openstack/contrib/drive_types.py +++ b/nova/api/openstack/contrib/drive_types.py @@ -18,18 +18,14 @@ """ The Drive Types extension for Virtual Storage Arrays""" - from webob import exc from nova.vsa import drive_types from nova import exception -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") -- cgit From 16cbba0838e9a2ac712b91b103dc794b0edebd00 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 28 Jul 2011 00:45:16 -0700 Subject: more commented code removed --- nova/api/openstack/contrib/virtual_storage_arrays.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index 68a00fd7d..842573f8a 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -180,8 +180,6 @@ class VsaVolumeDriveController(volumes.VolumeController): ]}}} def __init__(self): - # self.compute_api = compute.API() - # self.vsa_api = vsa.API() self.volume_api = volume.API() super(VsaVolumeDriveController, self).__init__() -- cgit From f4359a7789ae96a36aaab8f53aa3234d13b1725a Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 28 Jul 2011 15:54:02 -0700 Subject: returned vsa_manager, nova-manage arg and print changes --- nova/api/ec2/cloud.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ca1fef51f..0a0644351 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -965,6 +965,10 @@ class CloudController(object): vsa['vcType'] = p_vsa['vsa_instance_type'].get('name', None) else: vsa['vcType'] = None + + vols = self.volume_api.get_all_by_vsa(context, p_vsa['id'], "to") + vsa['volCount'] = 0 if vols is None else len(vols) + return vsa def create_vsa(self, context, **kwargs): -- cgit From bd39829cc1908cb5ead899c9659a5c516b073a4f Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Tue, 9 Aug 2011 16:55:51 -0700 Subject: merge with nova-1411. fixed --- nova/api/ec2/cloud.py | 2 +- nova/api/openstack/contrib/floating_ips.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9094f6b56..ac0ff713b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1149,7 +1149,7 @@ class CloudController(object): return {'driveTypeSet': [dict(drive) for drive in drives]} @staticmethod - def _convert_to_set(self, lst, label): + def _convert_to_set(lst, label): if lst is None or lst == []: return None if not isinstance(lst, list): diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 52c9c6cf9..2aba1068a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,7 +102,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) - + if 'fixed_ip' in ip: try: self.disassociate(req, id, '') -- cgit From b66ea57ae10bac1656e11663e273837dfae67814 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Fri, 12 Aug 2011 12:51:54 -0700 Subject: removed VSA/drive_type code from EC2 cloud. changed nova-manage not to use cloud APIs --- nova/api/ec2/__init__.py | 4 -- nova/api/ec2/cloud.py | 153 ----------------------------------------------- 2 files changed, 157 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 35c809547..8b6e47cfb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -268,10 +268,6 @@ class Authorizer(wsgi.Middleware): 'StartInstances': ['projectmanager', 'sysadmin'], 'StopInstances': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'], - 'CreateVsa': ['projectmanager', 'sysadmin'], - 'DeleteVsa': ['projectmanager', 'sysadmin'], - 'DescribeVsas': ['projectmanager', 'sysadmin'], - 'DescribeDriveTypes': ['projectmanager', 'sysadmin'], 'DescribeImages': ['all'], 'DeregisterImage': ['projectmanager', 'sysadmin'], 'RegisterImage': ['projectmanager', 'sysadmin'], diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ac0ff713b..87bba58c3 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -45,8 +45,6 @@ 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 @@ -184,7 +182,6 @@ 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): @@ -998,156 +995,6 @@ class CloudController(object): 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} - 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 - - vols = self.volume_api.get_all_by_vsa(context, p_vsa['id'], "to") - vsa['volCount'] = 0 if vols is None else len(vols) - - 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 = 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.audit(_("Describe VSAs")) - 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.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) - - dtype = drive_types.get_by_name(context, 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.update(context, dtype['id'], **changes) - return True - - def rename_drive_type(self, context, name, new_name): - drive_types.rename(context, name, new_name) - return True - - def delete_drive_type(self, context, name): - dtype = drive_types.get_by_name(context, name) - drive_types.delete(context, dtype['id']) - 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.get_by_name(context, name) - if drive['visible'] == visible: - drives.append(drive) - else: - drives = drive_types.get_all(context, visible) - # (VP-TMP): Change to EC2 compliant output later - return {'driveTypeSet': [dict(drive) for drive in drives]} - @staticmethod def _convert_to_set(lst, label): if lst is None or lst == []: -- cgit From cabf9cc8f29ad8c99971c434516e1b911f07f32f Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Wed, 17 Aug 2011 16:27:12 -0700 Subject: nova-manage VSA print & forced update_cap changes; fixed bug with report capabilities; added IP address to VSA APIs; added instances to APIs --- .../openstack/contrib/virtual_storage_arrays.py | 107 +++++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index 842573f8a..d6c4a5ef4 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -23,6 +23,7 @@ from webob import exc from nova import vsa from nova import volume +from nova import compute from nova import db from nova import quota from nova import exception @@ -31,6 +32,7 @@ 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 import servers from nova.api.openstack.contrib import volumes from nova.compute import instance_types @@ -40,7 +42,7 @@ FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.vsa") -def _vsa_view(context, vsa, details=False): +def _vsa_view(context, vsa, details=False, instances=None): """Map keys for vsa summary/detailed view.""" d = {} @@ -55,11 +57,27 @@ def _vsa_view(context, vsa, details=False): if 'vsa_instance_type' in vsa: d['vcType'] = vsa['vsa_instance_type'].get('name', None) else: - d['vcType'] = None + d['vcType'] = vsa['instance_type_id'] d['vcCount'] = vsa.get('vc_count') d['driveCount'] = vsa.get('vol_count') + d['ipAddress'] = None + for instance in instances: + fixed_addr = None + floating_addr = None + if instance['fixed_ips']: + fixed = instance['fixed_ips'][0] + fixed_addr = fixed['address'] + if fixed['floating_ips']: + floating_addr = fixed['floating_ips'][0]['address'] + + if floating_addr: + d['ipAddress'] = floating_addr + break + else: + d['ipAddress'] = d['ipAddress'] or fixed_addr + return d @@ -79,10 +97,12 @@ class VsaController(object): "vcType", "vcCount", "driveCount", + "ipAddress", ]}}} def __init__(self): self.vsa_api = vsa.API() + self.compute_api = compute.API() super(VsaController, self).__init__() def _items(self, req, details): @@ -90,8 +110,13 @@ class VsaController(object): context = req.environ['nova.context'] vsas = self.vsa_api.get_all(context) limited_list = common.limited(vsas, req) - res = [_vsa_view(context, vsa, details) for vsa in limited_list] - return {'vsaSet': res} + + vsa_list = [] + for vsa in limited_list: + instances = self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) + vsa_list.append(_vsa_view(context, vsa, details, instances)) + return {'vsaSet': vsa_list} def index(self, req): """Return a short list of VSAs.""" @@ -110,7 +135,10 @@ class VsaController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'vsa': _vsa_view(context, vsa, details=True)} + instances = self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) + + return {'vsa': _vsa_view(context, vsa, True, instances)} def create(self, req, body): """Create a new VSA.""" @@ -140,9 +168,12 @@ class VsaController(object): availability_zone=vsa.get('placement', {}).\ get('AvailabilityZone')) - result = self.vsa_api.create(context, **args) + vsa = self.vsa_api.create(context, **args) + + instances = self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) - return {'vsa': _vsa_view(context, result, details=True)} + return {'vsa': _vsa_view(context, vsa, True, instances)} def delete(self, req, id): """Delete a VSA.""" @@ -405,6 +436,61 @@ class VsaVPoolController(object): return faults.Fault(exc.HTTPBadRequest()) +class VsaVCController(servers.ControllerV11): + """The VSA Virtual Controller API controller for the OpenStack API.""" + + def __init__(self): + self.vsa_api = vsa.API() + self.compute_api = compute.API() + self.vsa_id = None # VP-TODO: temporary ugly hack + super(VsaVCController, self).__init__() + + def _get_servers(self, req, is_detail): + """Returns a list of servers, taking into account any search + options specified. + """ + + if self.vsa_id is None: + super(VsaVCController, self)._get_servers(req, is_detail) + + context = req.environ['nova.context'] + + search_opts = {'metadata': dict(vsa_id=str(self.vsa_id))} + instance_list = self.compute_api.get_all( + context, search_opts=search_opts) + + limited_list = self._limit_items(instance_list, req) + servers = [self._build_view(req, inst, is_detail)['server'] + for inst in limited_list] + return dict(servers=servers) + + def index(self, req, vsa_id): + """Return list of instances for particular VSA.""" + + LOG.audit(_("Index instances for VSA %s"), vsa_id) + + self.vsa_id = vsa_id # VP-TODO: temporary ugly hack + result = super(VsaVCController, self).detail(req) + self.vsa_id = None + return result + + def create(self, req, vsa_id, body): + """Create a new instance for VSA.""" + return faults.Fault(exc.HTTPBadRequest()) + + def update(self, req, vsa_id, id, body): + """Update VSA instance.""" + return faults.Fault(exc.HTTPBadRequest()) + + def delete(self, req, vsa_id, id): + """Delete VSA instance.""" + return faults.Fault(exc.HTTPBadRequest()) + + def show(self, req, vsa_id, id): + """Return data about the given instance.""" + return super(VsaVCController, self).show(req, id) + + class Virtual_storage_arrays(extensions.ExtensionDescriptor): def get_name(self): @@ -455,4 +541,11 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): collection_name='zadr-vsa')) resources.append(res) + res = extensions.ResourceExtension('instances', + VsaVCController(), + parent=dict( + member_name='vsa', + collection_name='zadr-vsa')) + resources.append(res) + return resources -- cgit From 805c1cec609b39ee5a0ba1517bf2f1d41e0c4fa9 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 15:05:35 -0700 Subject: allow specification of key pair/security group info via metadata extract metadata about keypair / security group configuration from server metadata sent on create. This allows users to use these extensions with their existing api implementations. Also remove the code that choose the first key pair in the tenant - since it seems to have been used during the development of os api --- nova/api/openstack/create_instance_helper.py | 21 ++++++++------------- nova/api/openstack/servers.py | 1 - 2 files changed, 8 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b4a08dac0..031b06921 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -73,20 +73,15 @@ class CreateInstanceHelper(object): if not 'server' in body: raise exc.HTTPUnprocessableEntity() - server_dict = body['server'] context = req.environ['nova.context'] + server_dict = body['server'] + metadata = server_dict.get('metadata', {}) password = self.controller._get_server_admin_password(server_dict) - key_name = None - key_data = None - # TODO(vish): Key pair access should move into a common library - # instead of being accessed directly from the db. - key_pairs = db.key_pair_get_all_by_user(context.elevated(), - context.user_id) - if key_pairs: - key_pair = key_pairs[0] - key_name = key_pair['name'] - key_data = key_pair['public_key'] + # NOTE(ja): extract key_name and security_group from metadata + # to use in os extensions for firewall & keypairs + key_name = metadata.get('key_name') + security_group = metadata.get('security_group') image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href @@ -155,8 +150,8 @@ class CreateInstanceHelper(object): display_name=name, display_description=name, key_name=key_name, - key_data=key_data, - metadata=server_dict.get('metadata', {}), + security_group=security_group, + metadata=metadata, injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 335ecad86..2cf4e3eda 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -146,7 +146,6 @@ class Controller(object): def create(self, req, body): """ Creates a new server for a given user """ extra_values = None - result = None extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) -- cgit From bbe414cba5d389b553fb3122a3a7dbc9c6d898f2 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 16:26:32 -0700 Subject: follow same pattern as userdata (not metadata apporach) --- nova/api/openstack/create_instance_helper.py | 27 +++++++++++++-------------- nova/api/openstack/views/servers.py | 3 +++ 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index e64a076c8..c2e9e76ac 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -75,13 +75,15 @@ class CreateInstanceHelper(object): context = req.environ['nova.context'] server_dict = body['server'] - metadata = server_dict.get('metadata', {}) password = self.controller._get_server_admin_password(server_dict) - # NOTE(ja): extract key_name and security_group from metadata - # to use in os extensions for firewall & keypairs - key_name = metadata.get('key_name') - security_group = metadata.get('security_group') + if not 'name' in server_dict: + msg = _("Server name is not defined") + raise exc.HTTPBadRequest(explanation=msg) + + name = server_dict['name'] + self._validate_server_name(name) + name = name.strip() image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href @@ -112,16 +114,13 @@ class CreateInstanceHelper(object): msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not 'name' in server_dict: - msg = _("Server name is not defined") - raise exc.HTTPBadRequest(explanation=msg) - zone_blob = server_dict.get('blob') - user_data = server_dict.get('user_data') availability_zone = server_dict.get('availability_zone') - name = server_dict['name'] - self._validate_server_name(name) - name = name.strip() + + # optional openstack extensions: + key_name = server_dict.get('key_name') + security_group = server_dict.get('security_group') + user_data = server_dict.get('user_data') reservation_id = server_dict.get('reservation_id') min_count = server_dict.get('min_count') @@ -152,7 +151,7 @@ class CreateInstanceHelper(object): display_description=name, key_name=key_name, security_group=security_group, - metadata=metadata, + metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..ac2de0c57 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -78,6 +78,9 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata + inst_dict['key_name'] = inst.get('key_name') + inst_dict['security_group'] = inst.get('security_group') + inst_dict['hostId'] = '' if inst.get('host'): inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() -- cgit From f4f0ce95946962be73cfc509b24fd000fc931198 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 18 Aug 2011 16:46:16 -0700 Subject: tests pass --- nova/api/openstack/views/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ac2de0c57..285228b30 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -78,8 +78,8 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata - inst_dict['key_name'] = inst.get('key_name') - inst_dict['security_group'] = inst.get('security_group') + inst_dict['key_name'] = inst.get('key_name', '') + inst_dict['security_group'] = inst.get('security_group', '') inst_dict['hostId'] = '' if inst.get('host'): -- cgit From 203309693fc2dd648b9d4b211686228557728c89 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 18 Aug 2011 19:05:40 -0700 Subject: remove security groups, improve exception handling, add tests --- nova/api/openstack/create_instance_helper.py | 5 +++-- nova/api/openstack/views/servers.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index c2e9e76ac..84d8c0c39 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -119,7 +119,6 @@ class CreateInstanceHelper(object): # optional openstack extensions: key_name = server_dict.get('key_name') - security_group = server_dict.get('security_group') user_data = server_dict.get('user_data') reservation_id = server_dict.get('reservation_id') @@ -150,7 +149,6 @@ class CreateInstanceHelper(object): display_name=name, display_description=name, key_name=key_name, - security_group=security_group, metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, @@ -168,6 +166,9 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except exception.KeypairNotFound as error: + msg = _("Invalid key_name provided.") + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 285228b30..c0df5abe6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -79,7 +79,6 @@ class ViewBuilder(object): inst_dict['metadata'] = metadata inst_dict['key_name'] = inst.get('key_name', '') - inst_dict['security_group'] = inst.get('security_group', '') inst_dict['hostId'] = '' if inst.get('host'): -- cgit From 3bd386cdba53f6a54a29e510c0f9eecf9b9ea7d9 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 19 Aug 2011 15:13:40 -0400 Subject: vm_state --> vm_states --- nova/api/ec2/cloud.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aebf92e3..4b69cc272 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -47,6 +47,7 @@ from nova import utils from nova import volume from nova.api.ec2 import ec2utils from nova.compute import instance_types +from nova.compute import vm_states from nova.image import s3 @@ -1039,11 +1040,10 @@ class CloudController(object): def _format_attr_instance_initiated_shutdown_behavior(instance, result): - state_description = instance['state_description'] - state_to_value = {'stopping': 'stop', - 'stopped': 'stop', - 'terminating': 'terminate'} - value = state_to_value.get(state_description) + vm_state = instance['vm_state'] + state_to_value = {vm_states.STOP: 'stop', + vm_states.DELETE: 'terminate'} + value = state_to_value.get(vm_state) if value: result['instanceInitiatedShutdownBehavior'] = value @@ -1198,8 +1198,8 @@ class CloudController(object): self._format_kernel_id(instance, i, 'kernelId') self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { - 'code': instance['state'], - 'name': instance['state_description']} + 'code': instance['power_state'], + 'name': instance['vm_state']} #FIXME fixed_addr = None floating_addr = None if instance['fixed_ips']: @@ -1618,22 +1618,22 @@ class CloudController(object): # stop the instance if necessary restart_instance = False if not no_reboot: - state_description = instance['state_description'] + vm_state = instance['vm_state'] # if the instance is in subtle state, refuse to proceed. - if state_description not in ('running', 'stopping', 'stopped'): + if vm_state not in (vm_states.ACTIVE, vm_states.STOP): raise exception.InstanceNotRunning(instance_id=ec2_instance_id) - if state_description == 'running': + if vm_state == vm_states.ACTIVE: restart_instance = True self.compute_api.stop(context, instance_id=instance_id) # wait instance for really stopped start_time = time.time() - while state_description != 'stopped': + while vm_state != vm_states.STOP: time.sleep(1) instance = self.compute_api.get(context, instance_id) - state_description = instance['state_description'] + vm_state = instance['vm_state'] # NOTE(yamahata): timeout and error. 1 hour for now for safety. # Is it too short/long? # Or is there any better way? -- cgit From c4e77b67a74cb0828bb9a7ccbedcaa1baeb6188d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 19 Aug 2011 18:34:34 -0400 Subject: Lots of modifications surrounding the OSAPI to remove any mention of dealing with power states and exclusively using vm_states and task_state modules. Currently there are still a number of tests failing, but this is a stopping place for today. --- nova/api/openstack/common.py | 52 ++++++++++++++++++------------------- nova/api/openstack/servers.py | 12 ++++----- nova/api/openstack/views/servers.py | 5 +++- 3 files changed, 35 insertions(+), 34 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index d9eb832f2..eae0fd916 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -27,7 +27,8 @@ from nova import flags from nova import log as logging from nova import quota from nova.api.openstack import wsgi -from nova.compute import power_state as compute_power_state +from nova.compute import vm_states +from nova.compute import task_state LOG = logging.getLogger('nova.api.openstack.common') @@ -38,36 +39,35 @@ XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' -_STATUS_MAP = { - None: 'BUILD', - compute_power_state.NOSTATE: 'BUILD', - compute_power_state.RUNNING: 'ACTIVE', - compute_power_state.BLOCKED: 'ACTIVE', - compute_power_state.SUSPENDED: 'SUSPENDED', - compute_power_state.PAUSED: 'PAUSED', - compute_power_state.SHUTDOWN: 'SHUTDOWN', - compute_power_state.SHUTOFF: 'SHUTOFF', - compute_power_state.CRASHED: 'ERROR', - compute_power_state.FAILED: 'ERROR', - compute_power_state.BUILDING: 'BUILD', +_STATE_MAP = { + vm_states.ACTIVE: 'ACTIVE', + vm_states.BUILD: 'BUILD', + vm_states.REBUILD: 'REBUILD', + vm_states.REBOOT: 'REBOOT', + vm_states.HARD_REBOOT: 'HARD_REBOOT', + vm_states.STOP: 'STOPPED', + vm_states.MIGRATE: 'MIGRATING', + vm_states.RESIZE: 'RESIZE', + vm_states.VERIFY_RESIZE: 'VERIFY_RESIZE', + vm_states.PAUSE: 'PAUSED', + vm_states.SUSPEND: 'SUSPENDED', + vm_states.RESCUE: 'RESCUE', + vm_states.ERROR: 'ERROR', } -def status_from_power_state(power_state): - """Map the power state to the server status string""" - return _STATUS_MAP[power_state] +def status_from_state(_vm_state, _task_state=None): + """Given vm_state and task_state, return a status string.""" + if _vm_state == vm_states.ACTIVE and _task_state == task_state.PASSWORD: + return "PASSWORD" + return _STATE_MAP.get(_vm_state, "UNKNOWN_STATE") -def power_states_from_status(status): - """Map the server status string to a list of power states""" - power_states = [] - for power_state, status_map in _STATUS_MAP.iteritems(): - # Skip the 'None' state - if power_state is None: - continue - if status.lower() == status_map.lower(): - power_states.append(power_state) - return power_states +def vm_state_from_status(status): + """Map the server status string to a vm state.""" + for state, status_string in _STATE_MAP.iteritems(): + if status.lower() == status_string.lower(): + return state def get_pagination_params(request): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 41e63ec3c..0a451caee 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -95,17 +95,15 @@ class Controller(object): search_opts['recurse_zones'] = utils.bool_from_str( search_opts.get('recurse_zones', False)) - # If search by 'status', we need to convert it to 'state' - # If the status is unknown, bail. - # Leave 'state' in search_opts so compute can pass it on to - # child zones.. + # If search by 'status', we need to convert it to 'vm_state' + # to pass on to child zones. if 'status' in search_opts: status = search_opts['status'] - search_opts['state'] = common.power_states_from_status(status) - if len(search_opts['state']) == 0: + state = common.vm_state_from_status(status) + if state is None: reason = _('Invalid server status: %(status)s') % locals() - LOG.error(reason) raise exception.InvalidInput(reason=reason) + search_opts['vm_state'] = state # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..e9a932b0e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -27,6 +27,7 @@ from nova.api.openstack.views import addresses as addresses_view from nova.api.openstack.views import flavors as flavors_view from nova.api.openstack.views import images as images_view from nova import utils +from nova.compute import vm_states class ViewBuilder(object): @@ -60,11 +61,13 @@ class ViewBuilder(object): def _build_detail(self, inst): """Returns a detailed model of a server.""" + vm_state = inst.get('vm_state', vm_states.BUILD) + task_state = inst.get('task_state') inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'status': common.status_from_power_state(inst.get('state'))} + 'status': common.status_from_state(vm_state, task_state)} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() -- cgit From f82d2d309a0f826522854fe331d1c53b8c6d6879 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 09:54:33 -0400 Subject: Ec2 API updates. --- nova/api/ec2/cloud.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4b69cc272..b7c7d2e12 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,6 +79,29 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} +# EC2 API: Valid Values: +# pending | running | shutting-down | terminated | stopping | stopped +_STATE_DESCRIPTION_MAP = { + vm_states.ACTIVE: 'running', + vm_states.BUILD: 'pending', + vm_states.REBUILD: 'pending', + vm_states.REBOOT: 'reboot', + vm_states.DELETE: 'terminated', + vm_states.STOP: 'stopped', + vm_states.MIGRATE: 'migrate', + vm_states.RESIZE: 'resize', + vm_states.VERIFY_RESIZE: 'verify_resize', + vm_states.PAUSE: 'pause', + vm_states.SUSPEND: 'suspend', + vm_states.RESCUE: 'rescue' +} + + +def state_description_from_vm_state(vm_state): + """Map the vm state to the server status string""" + return _STATE_DESCRIPTION_MAP[vm_state] + + # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' _DEFAULT_MAPPINGS = {'ami': 'sda1', @@ -1199,7 +1222,7 @@ class CloudController(object): self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { 'code': instance['power_state'], - 'name': instance['vm_state']} #FIXME + 'name': state_description_from_vm_state(instance['vm_state'])} fixed_addr = None floating_addr = None if instance['fixed_ips']: -- cgit From 44aea954e5efa7d94d8333ddbf54dab6464018a0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 10:01:13 -0400 Subject: Renamed task_state to task_states... --- nova/api/openstack/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index eae0fd916..778c1e514 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -28,7 +28,7 @@ from nova import log as logging from nova import quota from nova.api.openstack import wsgi from nova.compute import vm_states -from nova.compute import task_state +from nova.compute import task_states LOG = logging.getLogger('nova.api.openstack.common') @@ -56,9 +56,9 @@ _STATE_MAP = { } -def status_from_state(_vm_state, _task_state=None): +def status_from_state(_vm_state, task_state=None): """Given vm_state and task_state, return a status string.""" - if _vm_state == vm_states.ACTIVE and _task_state == task_state.PASSWORD: + if _vm_state == vm_states.ACTIVE and task_state == task_states.PASSWORD: return "PASSWORD" return _STATE_MAP.get(_vm_state, "UNKNOWN_STATE") -- cgit From d60f813201df345507ce0aca7bed0f8b719aabfe Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 22 Aug 2011 11:59:08 -0400 Subject: Fixes/updates to make test_cloud pass. --- nova/api/ec2/cloud.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b7c7d2e12..8bddf3032 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -82,6 +82,7 @@ def _gen_key(context, user_id, key_name): # EC2 API: Valid Values: # pending | running | shutting-down | terminated | stopping | stopped _STATE_DESCRIPTION_MAP = { + None: 'pending', vm_states.ACTIVE: 'running', vm_states.BUILD: 'pending', vm_states.REBUILD: 'pending', -- cgit From 7726b3d763a136347f2324e630f0a3cdc60a045b Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 14:08:03 -0700 Subject: Simple usage extension for nova. Uses db to calculate tenant_usage for specified time periods. Methods: index: return a list of tenant_usages, with option of incuding detailed server_usage show: returns a specific tenant_usage object tenant_usage object: tenant_usage.total_memory_mb_usage: sum of memory_mb * hours for all instances in tenant for this period tenant_usage.total_local_gb_usage: sum of local_gb * hours for all instances in tenant for this period tenant_usage.total_vcpus_usage: sum of vcpus * hours for all instances in tenant for this period tenant_usage.total_hours: sum of all instance hours for this period tenant_usage.server_usages: A detailed list of server_usages, which describe the usage of a specific server For larger instances db tables, indexes on instance.launched_at and instance.terminated_at should significantly help performance. --- nova/api/openstack/contrib/simple_tenant_usage.py | 268 ++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 nova/api/openstack/contrib/simple_tenant_usage.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py new file mode 100644 index 000000000..d578b2b67 --- /dev/null +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -0,0 +1,268 @@ +# 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 urlparse +import webob + +from datetime import datetime +from nova import db +from nova import exception +from nova import flags +from nova.compute import instance_types +from nova.api.openstack import extensions +from nova.api.openstack import views +from nova.db.sqlalchemy.session import get_session +from webob import exc + + +FLAGS = flags.FLAGS + +INSTANCE_FIELDS = ['id', + 'image_ref', + 'project_id', + 'user_id', + 'display_name', + 'state_description', + 'instance_type_id', + 'launched_at', + 'terminated_at'] + + +class SimpleTenantUsageController(object): + + def _get_instances_for_time_period(self, period_start, period_stop, + tenant_id): + tenant_clause = '' + if tenant_id: + tenant_clause = " and project_id='%s'" % tenant_id + + conn = get_session().connection() + rows = conn.execute("select %s from instances where \ + (terminated_at is NULL or terminated_at > '%s') \ + and (launched_at < '%s') %s" %\ + (','.join(INSTANCE_FIELDS), + period_start.isoformat(' '),\ + period_stop.isoformat(' '), + tenant_clause)).fetchall() + + return rows + + def _hours_for(self, instance, period_start, period_stop): + launched_at = instance['launched_at'] + terminated_at = instance['terminated_at'] + if terminated_at is not None: + if not isinstance(terminated_at, datetime): + terminated_at = datetime.strptime(terminated_at, + "%Y-%m-%d %H:%M:%S.%f") + + if launched_at is not None: + if not isinstance(launched_at, datetime): + launched_at = datetime.strptime(launched_at, + "%Y-%m-%d %H:%M:%S.%f") + + if terminated_at and terminated_at < period_start: + return 0 + # nothing if it started after the usage report ended + if launched_at and launched_at > period_stop: + return 0 + if launched_at: + # if instance launched after period_started, don't charge for first + start = max(launched_at, period_start) + if terminated_at: + # if instance stopped before period_stop, don't charge after + stop = min(period_stop, terminated_at) + else: + # instance is still running, so charge them up to current time + stop = period_stop + dt = stop - start + seconds = dt.days * 3600 * 24 + dt.seconds\ + + dt.microseconds / 100000.0 + + return seconds / 3600.0 + else: + # instance hasn't launched, so no charge + return 0 + + def _usage_for_period(self, context, period_start, + period_stop, tenant_id=None, detailed=True): + + rows = self._get_instances_for_time_period(period_start, + period_stop, + tenant_id) + rval = {} + flavors = {} + + for row in rows: + info = {} + for i in range(len(INSTANCE_FIELDS)): + info[INSTANCE_FIELDS[i]] = row[i] + info['hours'] = self._hours_for(info, period_start, period_stop) + flavor_type = info['instance_type_id'] + + if not flavors.get(flavor_type): + try: + flavors[flavor_type] = db.instance_type_get(context, + info['instance_type_id']) + except exception.InstanceTypeNotFound: + # can't bill if there is no instance type + continue + + flavor = flavors[flavor_type] + + info['name'] = info['display_name'] + del(info['display_name']) + + info['memory_mb'] = flavor['memory_mb'] + info['local_gb'] = flavor['local_gb'] + info['vcpus'] = flavor['vcpus'] + + info['tenant_id'] = info['project_id'] + del(info['project_id']) + + info['flavor'] = flavor['name'] + del(info['instance_type_id']) + + info['started_at'] = info['launched_at'] + del(info['launched_at']) + + info['ended_at'] = info['terminated_at'] + del(info['terminated_at']) + + if info['ended_at']: + info['state'] = 'terminated' + else: + info['state'] = info['state_description'] + + del(info['state_description']) + + now = datetime.utcnow() + + if info['state'] == 'terminated': + delta = self._parse_datetime(info['ended_at'])\ + - self._parse_datetime(info['started_at']) + else: + delta = now - self._parse_datetime(info['started_at']) + + info['uptime'] = delta.days * 24 * 60 + delta.seconds + + if not info['tenant_id'] in rval: + summary = {} + summary['tenant_id'] = info['tenant_id'] + if detailed: + summary['server_usages'] = [] + summary['total_local_gb_usage'] = 0 + summary['total_vcpus_usage'] = 0 + summary['total_memory_mb_usage'] = 0 + summary['total_hours'] = 0 + summary['start'] = period_start + summary['stop'] = period_stop + rval[info['tenant_id']] = summary + + summary = rval[info['tenant_id']] + summary['total_local_gb_usage'] += info['local_gb'] * info['hours'] + summary['total_vcpus_usage'] += info['vcpus'] * info['hours'] + summary['total_memory_mb_usage'] += info['memory_mb']\ + * info['hours'] + + summary['total_hours'] += info['hours'] + if detailed: + summary['server_usages'].append(info) + + return rval.values() + + def _parse_datetime(self, dtstr): + if isinstance(dtstr, datetime): + return dtstr + try: + return datetime.strptime(dtstr, "%Y-%m-%dT%H:%M:%S") + except: + try: + return datetime.strptime(dtstr, "%Y-%m-%dT%H:%M:%S.%f") + except: + return datetime.strptime(dtstr, "%Y-%m-%d %H:%M:%S.%f") + + def _get_datetime_range(self, req): + qs = req.environ.get('QUERY_STRING', '') + env = urlparse.parse_qs(qs) + period_start = self._parse_datetime(env.get('start', + [datetime.utcnow().isoformat()])[0]) + period_stop = self._parse_datetime(env.get('end', + [datetime.utcnow().isoformat()])[0]) + + detailed = bool(env.get('detailed', False)) + return (period_start, period_stop, detailed) + + def index(self, req): + """Retrive tenant_usage for all tenants""" + (period_start, period_stop, detailed) = self._get_datetime_range(req) + context = req.environ['nova.context'] + + if not context.is_admin and FLAGS.allow_admin_api: + return webob.Response(status_int=403) + + usages = self._usage_for_period(context, + period_start, + period_stop, + detailed=detailed) + return {'tenant_usages': usages} + + def show(self, req, id): + """Retrive tenant_usage for a specified tenant""" + (period_start, period_stop, ignore) = self._get_datetime_range(req) + context = req.environ['nova.context'] + + if not context.is_admin and FLAGS.allow_admin_api: + if id != context.project_id: + return webob.Response(status_int=403) + + usage = self._usage_for_period(context, + period_start, + period_stop, + id, + detailed=True) + if len(usage): + usage = usage[0] + else: + usage = {} + return {'tenant_usage': usage} + + +class Simple_tenant_usage(extensions.ExtensionDescriptor): + + def get_name(self): + return "Simple_tenant_usage" + + def get_alias(self): + return "os-simple-tenant-usage" + + def get_description(self): + return "Simple tenant usage extension" + + def get_namespace(self): + return "http://docs.openstack.org/ext/os-simple-tenant-usage/api/v1.1" + + def get_updated(self): + return "2011-08-19T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('os-simple-tenant-usage', + SimpleTenantUsageController()) + resources.append(res) + + return resources -- cgit From 4b0944731a25d3cfcd30358619376dedf2251701 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 14:31:26 -0700 Subject: some readability fixes per ja feedback --- nova/api/openstack/contrib/simple_tenant_usage.py | 29 ++++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index d578b2b67..80d819365 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -97,8 +97,8 @@ class SimpleTenantUsageController(object): # instance hasn't launched, so no charge return 0 - def _usage_for_period(self, context, period_start, - period_stop, tenant_id=None, detailed=True): + def _tenant_usages_for_period(self, context, period_start, + period_stop, tenant_id=None, detailed=True): rows = self._get_instances_for_time_period(period_start, period_stop, @@ -208,32 +208,33 @@ class SimpleTenantUsageController(object): def index(self, req): """Retrive tenant_usage for all tenants""" - (period_start, period_stop, detailed) = self._get_datetime_range(req) context = req.environ['nova.context'] if not context.is_admin and FLAGS.allow_admin_api: return webob.Response(status_int=403) - usages = self._usage_for_period(context, - period_start, - period_stop, - detailed=detailed) + (period_start, period_stop, detailed) = self._get_datetime_range(req) + usages = self._tenant_usages_for_period(context, + period_start, + period_stop, + detailed=detailed) return {'tenant_usages': usages} def show(self, req, id): """Retrive tenant_usage for a specified tenant""" - (period_start, period_stop, ignore) = self._get_datetime_range(req) + tenant_id = id context = req.environ['nova.context'] if not context.is_admin and FLAGS.allow_admin_api: - if id != context.project_id: + if tenant_id != context.project_id: return webob.Response(status_int=403) - usage = self._usage_for_period(context, - period_start, - period_stop, - id, - detailed=True) + (period_start, period_stop, ignore) = self._get_datetime_range(req) + usage = self._tenant_usages_for_period(context, + period_start, + period_stop, + tenant_id=tenant_id, + detailed=True) if len(usage): usage = usage[0] else: -- cgit From de0a17310e7228aa96263243851a89fb016f9730 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 15:21:31 -0700 Subject: remove extra spaces --- nova/api/openstack/contrib/simple_tenant_usage.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 80d819365..5f4218237 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -43,7 +43,6 @@ INSTANCE_FIELDS = ['id', class SimpleTenantUsageController(object): - def _get_instances_for_time_period(self, period_start, period_stop, tenant_id): tenant_clause = '' @@ -243,7 +242,6 @@ class SimpleTenantUsageController(object): class Simple_tenant_usage(extensions.ExtensionDescriptor): - def get_name(self): return "Simple_tenant_usage" -- cgit From 2fbaac5e07b558d7829253915523f073b07e24d4 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 23 Aug 2011 10:57:47 -0400 Subject: PEP8 fixes --- nova/api/ec2/cloud.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a7a343938..c5a360426 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -93,7 +93,7 @@ _STATE_DESCRIPTION_MAP = { vm_states.VERIFY_RESIZE: 'verify_resize', vm_states.PAUSED: 'pause', vm_states.SUSPENDED: 'suspend', - vm_states.RESCUED: 'rescue' + vm_states.RESCUED: 'rescue', } @@ -104,10 +104,12 @@ def state_description_from_vm_state(vm_state): # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' -_DEFAULT_MAPPINGS = {'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3'} +_DEFAULT_MAPPINGS = { + 'ami': 'sda1', + 'ephemeral0': 'sda2', + 'root': _DEFAULT_ROOT_DEVICE_NAME, + 'swap': 'sda3', +} def _parse_block_device_mapping(bdm): @@ -1064,8 +1066,10 @@ class CloudController(object): def _format_attr_instance_initiated_shutdown_behavior(instance, result): vm_state = instance['vm_state'] - state_to_value = {vm_states.STOPPED: 'stop', - vm_states.DELETED: 'terminate'} + state_to_value = { + vm_states.STOPPED: 'stop', + vm_states.DELETED: 'terminate', + } value = state_to_value.get(vm_state) if value: result['instanceInitiatedShutdownBehavior'] = value -- cgit From da02fc6e4191bdbbb2015b78f9c3fe5045bb0460 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Aug 2011 15:14:09 -0700 Subject: Fix not found exceptions to properly use ec2_ips for not found --- nova/api/ec2/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5430f443d..363dad7cd 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -392,17 +392,19 @@ class Executor(wsgi.Application): except exception.InstanceNotFound as ex: LOG.info(_('InstanceNotFound raised: %s'), unicode(ex), context=context) - return self._error(req, context, type(ex).__name__, ex.message) + ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id']) + message = ex.message % {'instance_id': ec2_id} + return self._error(req, context, type(ex).__name__, message) except exception.VolumeNotFound as ex: LOG.info(_('VolumeNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_vol_id(ex.volume_id) - message = _('Volume %s not found') % ec2_id + ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id']) + message = ex.message % {'volume_id': ec2_id} return self._error(req, context, type(ex).__name__, message) except exception.SnapshotNotFound as ex: LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_snap_id(ex.snapshot_id) + ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id']) message = _('Snapshot %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: -- cgit From 64f946a6a0a6e08d7046ab98776928abe24f8d93 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 13:01:20 -0400 Subject: Fix for trying rebuilds when instance is not active. --- nova/api/openstack/servers.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4ff9264a6..c5fdda1cf 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -604,9 +604,8 @@ class ControllerV10(Controller): try: self.compute_api.rebuild(context, instance_id, image_id) - except exception.BuildInProgress: - msg = _("Instance %s is currently being rebuilt.") % instance_id - LOG.debug(msg) + except exception.RebuildRequiresActiveInstance: + msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -742,9 +741,8 @@ class ControllerV11(Controller): try: self.compute_api.rebuild(context, instance_id, image_href, name, metadata, personalities) - except exception.BuildInProgress: - msg = _("Instance %s is currently being rebuilt.") % instance_id - LOG.debug(msg) + except exception.RebuildRequiresActiveInstance: + msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) -- cgit From a6bf7c0b2522509dda8dd5e537fad49665aa2af2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 24 Aug 2011 13:27:23 -0400 Subject: Added DELETED status to OSAPI just in case. --- nova/api/openstack/common.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 90b2095b8..07c6fbd11 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -76,6 +76,9 @@ _STATE_MAP = { vm_states.ERROR: { 'default': 'ERROR', }, + vm_states.DELETED: { + 'default': 'DELETED', + }, } -- cgit From 0343a328e66557abda9d0817558ad09a73962eb9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 24 Aug 2011 14:39:47 -0700 Subject: change snapshot msg too --- nova/api/ec2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 363dad7cd..1e176e52d 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -405,7 +405,7 @@ class Executor(wsgi.Application): LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex), context=context) ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id']) - message = _('Snapshot %s not found') % ec2_id + message = ex.message % {'snapshot_id': ec2_id} return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: LOG.info(_('NotFound raised: %s'), unicode(ex), context=context) -- cgit From 48cd9689de31e408c792052747f714a9dbe1f8f7 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Wed, 24 Aug 2011 15:51:29 -0700 Subject: added virtio flag; associate address for VSA; cosmetic changes. Prior to volume_types merge --- nova/api/openstack/contrib/drive_types.py | 1 - .../openstack/contrib/virtual_storage_arrays.py | 49 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py index f2cbd3715..1aa65374f 100644 --- a/nova/api/openstack/contrib/drive_types.py +++ b/nova/api/openstack/contrib/drive_types.py @@ -2,7 +2,6 @@ # Copyright (c) 2011 Zadara Storage Inc. # 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 diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index d6c4a5ef4..81dbc9e1f 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -2,7 +2,6 @@ # Copyright (c) 2011 Zadara Storage Inc. # 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 @@ -24,6 +23,7 @@ from webob import exc from nova import vsa from nova import volume from nova import compute +from nova import network from nova import db from nova import quota from nova import exception @@ -103,6 +103,7 @@ class VsaController(object): def __init__(self): self.vsa_api = vsa.API() self.compute_api = compute.API() + self.network_api = network.API() super(VsaController, self).__init__() def _items(self, req, details): @@ -186,6 +187,48 @@ class VsaController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + def associate_address(self, req, id, body): + """ /zadr-vsa/{vsa_id}/associate_address + auto or manually associate an IP to VSA + """ + context = req.environ['nova.context'] + + if body is None: + ip = 'auto' + else: + ip = body.get('ipAddress', 'auto') + + LOG.audit(_("Associate address %(ip)s to VSA %(id)s"), + locals(), context=context) + + try: + instances = self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(id))}) + + if instances is None or len(instances)==0: + return faults.Fault(exc.HTTPNotFound()) + + for instance in instances: + self.network_api.allocate_for_instance(context, instance, vpn=False) + return + + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + def disassociate_address(self, req, id, body): + """ /zadr-vsa/{vsa_id}/disassociate_address + auto or manually associate an IP to VSA + """ + context = req.environ['nova.context'] + + if body is None: + ip = 'auto' + else: + ip = body.get('ipAddress', 'auto') + + LOG.audit(_("Disassociate address from VSA %(id)s"), + locals(), context=context) + class VsaVolumeDriveController(volumes.VolumeController): """The base class for VSA volumes & drives. @@ -515,7 +558,9 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): VsaController(), collection_actions={'detail': 'GET'}, member_actions={'add_capacity': 'POST', - 'remove_capacity': 'POST'}) + 'remove_capacity': 'POST', + 'associate_address': 'POST', + 'disassociate_address': 'POST'}) resources.append(res) res = extensions.ResourceExtension('volumes', -- cgit From 6e14007c09a465374d1b50cd00549c1be6dc536c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 11:11:51 -0400 Subject: Removed RESIZE-CONFIRM hack. --- nova/api/openstack/views/servers.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 8f8d04ca0..b0daeb7a8 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -21,8 +21,6 @@ import hashlib import os from nova import exception -import nova.compute -import nova.context from nova.api.openstack import common from nova.api.openstack.views import addresses as addresses_view from nova.api.openstack.views import flavors as flavors_view @@ -70,12 +68,6 @@ class ViewBuilder(object): 'name': inst['display_name'], 'status': common.status_from_state(vm_state, task_state)} - ctxt = nova.context.get_admin_context() - compute_api = nova.compute.API() - - if compute_api.has_finished_migration(ctxt, inst['uuid']): - inst_dict['status'] = 'RESIZE-CONFIRM' - # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): -- cgit From 6758779249d490fd21bfdeae6d40adfc33d8cd17 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 12:50:36 -0400 Subject: Reverted two mistakes when looking over full diff. --- nova/api/ec2/cloud.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c5a360426..cf9437b08 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -104,12 +104,10 @@ def state_description_from_vm_state(vm_state): # TODO(yamahata): hypervisor dependent default device name _DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' -_DEFAULT_MAPPINGS = { - 'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3', -} +_DEFAULT_MAPPINGS = {'ami': 'sda1', + 'ephemeral0': 'sda2', + 'root': _DEFAULT_ROOT_DEVICE_NAME, + 'swap': 'sda3'} def _parse_block_device_mapping(bdm): -- cgit From d8d4aff908925b2f351e77291f4a8f394994063d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 16:38:38 -0400 Subject: Review feedback. --- nova/api/ec2/cloud.py | 8 +++++--- nova/api/openstack/common.py | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cf9437b08..ac247a0ef 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,7 +79,9 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} -# EC2 API: Valid Values: +# EC2 API can return the following values as documented in the EC2 API +# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ +# ApiReference-ItemType-InstanceStateType.html # pending | running | shutting-down | terminated | stopping | stopped _STATE_DESCRIPTION_MAP = { None: 'pending', @@ -1065,8 +1067,8 @@ class CloudController(object): result): vm_state = instance['vm_state'] state_to_value = { - vm_states.STOPPED: 'stop', - vm_states.DELETED: 'terminate', + vm_states.STOPPED: 'stopped', + vm_states.DELETED: 'terminated', } value = state_to_value.get(vm_state) if value: diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 07c6fbd11..bdbae0271 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -84,10 +84,11 @@ _STATE_MAP = { def status_from_state(vm_state, task_state='default'): """Given vm_state and task_state, return a status string.""" - LOG.debug("Generating status for vm_state=%(vm_state)s " - "task_state=%(task_state)s." % locals()) task_map = _STATE_MAP.get(vm_state, dict(default='UNKNOWN_STATE')) - return task_map.get(task_state, task_map['default']) + status = task_map.get(task_state, task_map['default']) + LOG.debug("Generated %(status)s from vm_state=%(vm_state)s " + "task_state=%(task_state)s." % locals()) + return status def vm_state_from_status(status): -- cgit From c316782f8879ef321c4545b04bc9d24e11bb4ee6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 25 Aug 2011 17:27:10 -0400 Subject: review feedback --- nova/api/ec2/cloud.py | 3 +-- nova/api/openstack/common.py | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ac247a0ef..fe44191c8 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -92,7 +92,6 @@ _STATE_DESCRIPTION_MAP = { vm_states.STOPPED: 'stopped', vm_states.MIGRATING: 'migrate', vm_states.RESIZING: 'resize', - vm_states.VERIFY_RESIZE: 'verify_resize', vm_states.PAUSED: 'pause', vm_states.SUSPENDED: 'suspend', vm_states.RESCUED: 'rescue', @@ -101,7 +100,7 @@ _STATE_DESCRIPTION_MAP = { def state_description_from_vm_state(vm_state): """Map the vm state to the server status string""" - return _STATE_DESCRIPTION_MAP[vm_state] + return _STATE_DESCRIPTION_MAP.get(vm_state, vm_state) # TODO(yamahata): hypervisor dependent default device name diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index bdbae0271..d743a66ef 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -43,8 +43,8 @@ _STATE_MAP = { vm_states.ACTIVE: { 'default': 'ACTIVE', task_states.REBOOTING: 'REBOOT', - task_states.HARD_REBOOTING: 'HARD_REBOOT', - task_states.PASSWORD: 'PASSWORD', + task_states.UPDATING_PASSWORD: 'PASSWORD', + task_states.RESIZE_VERIFY: 'VERIFY_RESIZE', }, vm_states.BUILDING: { 'default': 'BUILD', @@ -61,9 +61,6 @@ _STATE_MAP = { vm_states.RESIZING: { 'default': 'RESIZE', }, - vm_states.VERIFY_RESIZE: { - 'default': 'VERIFY_RESIZE', - }, vm_states.PAUSED: { 'default': 'PAUSED', }, -- cgit From 2cf0b67e08e1608bd717ffadd41d5029db2b4a3a Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 25 Aug 2011 21:56:45 +0000 Subject: Fix glance image authorization check now that glance can do authorization checks on its own; use correct image service when looking for ramdisk, etc.; fix a couple of PEP8 errors --- nova/api/openstack/create_instance_helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..c428a8209 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -98,7 +98,7 @@ class CreateInstanceHelper(object): try: image_service, image_id = nova.image.get_image_service(image_href) kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( - req, image_id) + req, image_service, image_id) images = set([str(x['id']) for x in image_service.index(context)]) assert str(image_id) in images except Exception, e: @@ -248,12 +248,12 @@ class CreateInstanceHelper(object): msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) - def _get_kernel_ramdisk_from_image(self, req, image_id): + def _get_kernel_ramdisk_from_image(self, req, image_service, image_id): """Fetch an image from the ImageService, then if present, return the associated kernel and ramdisk image IDs. """ context = req.environ['nova.context'] - image_meta = self._image_service.show(context, image_id) + image_meta = image_service.show(context, image_id) # NOTE(sirp): extracted to a separate method to aid unit-testing, the # new method doesn't need a request obj or an ImageService stub kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( -- cgit From 4834b920e3186712ab56e65a88c2e8c838d16f9c Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 25 Aug 2011 18:38:35 -0700 Subject: VSA code redesign. Drive types completely replaced by Volume types --- nova/api/openstack/contrib/drive_types.py | 143 --------------------- .../openstack/contrib/virtual_storage_arrays.py | 54 ++++---- 2 files changed, 32 insertions(+), 165 deletions(-) delete mode 100644 nova/api/openstack/contrib/drive_types.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/drive_types.py b/nova/api/openstack/contrib/drive_types.py deleted file mode 100644 index 1aa65374f..000000000 --- a/nova/api/openstack/contrib/drive_types.py +++ /dev/null @@ -1,143 +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 Drive Types extension for Virtual Storage Arrays""" - -from webob import exc - -from nova.vsa import drive_types -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 - -LOG = logging.getLogger("nova.api.drive_types") - - -def _drive_type_view(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 - - -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 index(self, req): - """Returns a list of drive types.""" - - context = req.environ['nova.context'] - dtypes = drive_types.get_all(context) - limited_list = common.limited(dtypes, req) - res = [_drive_type_view(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.get(context, id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - return {'drive_type': _drive_type_view(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.create(context, - type=type, - size_gb=size, - rpm=rpm, - capabilities=capabilities, - name=name) - - return {'drive_type': _drive_type_view(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_types.delete(context, id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - -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 index 81dbc9e1f..f3e4fc849 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -106,6 +106,10 @@ class VsaController(object): self.network_api = network.API() super(VsaController, self).__init__() + def _get_instances_by_vsa_id(self, context, id): + return self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(id))}) + def _items(self, req, details): """Return summary or detailed list of VSAs.""" context = req.environ['nova.context'] @@ -114,8 +118,7 @@ class VsaController(object): vsa_list = [] for vsa in limited_list: - instances = self.compute_api.get_all(context, - search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) + instances = self._get_instances_by_vsa_id(context, vsa.get('id')) vsa_list.append(_vsa_view(context, vsa, details, instances)) return {'vsaSet': vsa_list} @@ -136,9 +139,7 @@ class VsaController(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - instances = self.compute_api.get_all(context, - search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) - + instances = self._get_instances_by_vsa_id(context, vsa.get('id')) return {'vsa': _vsa_view(context, vsa, True, instances)} def create(self, req, body): @@ -171,9 +172,7 @@ class VsaController(object): vsa = self.vsa_api.create(context, **args) - instances = self.compute_api.get_all(context, - search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))}) - + instances = self._get_instances_by_vsa_id(context, vsa.get('id')) return {'vsa': _vsa_view(context, vsa, True, instances)} def delete(self, req, id): @@ -202,14 +201,14 @@ class VsaController(object): locals(), context=context) try: - instances = self.compute_api.get_all(context, - search_opts={'metadata': dict(vsa_id=str(id))}) - - if instances is None or len(instances)==0: + instances = self._get_instances_by_vsa_id(context, id) + if instances is None or len(instances) == 0: return faults.Fault(exc.HTTPNotFound()) for instance in instances: - self.network_api.allocate_for_instance(context, instance, vpn=False) + self.network_api.allocate_for_instance(context, instance, + vpn=False) + # Placeholder return except exception.NotFound: @@ -228,6 +227,7 @@ class VsaController(object): LOG.audit(_("Disassociate address from VSA %(id)s"), locals(), context=context) + # Placeholder class VsaVolumeDriveController(volumes.VolumeController): @@ -255,6 +255,7 @@ class VsaVolumeDriveController(volumes.VolumeController): def __init__(self): self.volume_api = volume.API() + self.vsa_api = vsa.API() super(VsaVolumeDriveController, self).__init__() def _translation(self, context, vol, vsa_id, details): @@ -264,7 +265,7 @@ class VsaVolumeDriveController(volumes.VolumeController): translation = volumes.translate_volume_summary_view d = translation(context, vol) - d['vsaId'] = vol[self.direction] + d['vsaId'] = vsa_id d['name'] = vol['name'] return d @@ -276,8 +277,9 @@ class VsaVolumeDriveController(volumes.VolumeController): 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): + own_vsa_id = self.volume_api.get_volume_metadata_value(volume_ref, + self.direction) + if own_vsa_id != 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() @@ -286,8 +288,8 @@ class VsaVolumeDriveController(volumes.VolumeController): """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]) + vols = self.volume_api.get_all(context, + search_opts={'metadata': {self.direction: str(vsa_id)}}) limited_list = common.limited(vols, req) res = [self._translation(context, vol, vsa_id, details) \ @@ -317,11 +319,19 @@ class VsaVolumeDriveController(volumes.VolumeController): size = vol['size'] LOG.audit(_("Create volume of %(size)s GB from VSA ID %(vsa_id)s"), locals(), context=context) + try: + # create is supported for volumes only (drives created through VSA) + volume_type = self.vsa_api.get_vsa_volume_type(context) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) - new_volume = self.volume_api.create(context, size, None, - vol.get('displayName'), - vol.get('displayDescription'), - from_vsa_id=vsa_id) + new_volume = self.volume_api.create(context, + size, + None, + vol.get('displayName'), + vol.get('displayDescription'), + volume_type=volume_type, + metadata=dict(from_vsa_id=str(vsa_id))) return {self.object: self._translation(context, new_volume, vsa_id, True)} -- cgit From 59e9adb8e2ef39474a04ead76975a1fc3f913550 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 25 Aug 2011 19:09:50 -0700 Subject: cosmetic cleanup --- nova/api/openstack/contrib/virtual_storage_arrays.py | 6 +++--- nova/api/openstack/contrib/volumes.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/virtual_storage_arrays.py b/nova/api/openstack/contrib/virtual_storage_arrays.py index f3e4fc849..e09736a28 100644 --- a/nova/api/openstack/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/contrib/virtual_storage_arrays.py @@ -260,9 +260,9 @@ class VsaVolumeDriveController(volumes.VolumeController): def _translation(self, context, vol, vsa_id, details): if details: - translation = volumes.translate_volume_detail_view + translation = volumes._translate_volume_detail_view else: - translation = volumes.translate_volume_summary_view + translation = volumes._translate_volume_summary_view d = translation(context, vol) d['vsaId'] = vsa_id @@ -559,7 +559,7 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): return "http://docs.openstack.org/ext/vsa/api/v1.1" def get_updated(self): - return "2011-06-29T00:00:00+00:00" + return "2011-08-25T00:00:00+00:00" def get_resources(self): resources = [] diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 8c3898867..d62225e58 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -37,17 +37,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 = {} @@ -114,7 +114,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.""" @@ -130,11 +130,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.""" @@ -175,7 +175,7 @@ class VolumeController(object): # Work around problem that instance is lazy-loaded... new_volume = self.volume_api.get(context, new_volume['id']) - retval = translate_volume_detail_view(context, new_volume) + retval = _translate_volume_detail_view(context, new_volume) return {'volume': retval} -- cgit From 400427ab786779109d49b27eda2fe9e246503dd6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 26 Aug 2011 16:17:40 -0700 Subject: use db layer for aggregation --- nova/api/openstack/contrib/simple_tenant_usage.py | 72 +++++++---------------- 1 file changed, 20 insertions(+), 52 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 5f4218237..16e712815 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -31,35 +31,7 @@ from webob import exc FLAGS = flags.FLAGS -INSTANCE_FIELDS = ['id', - 'image_ref', - 'project_id', - 'user_id', - 'display_name', - 'state_description', - 'instance_type_id', - 'launched_at', - 'terminated_at'] - - class SimpleTenantUsageController(object): - def _get_instances_for_time_period(self, period_start, period_stop, - tenant_id): - tenant_clause = '' - if tenant_id: - tenant_clause = " and project_id='%s'" % tenant_id - - conn = get_session().connection() - rows = conn.execute("select %s from instances where \ - (terminated_at is NULL or terminated_at > '%s') \ - and (launched_at < '%s') %s" %\ - (','.join(INSTANCE_FIELDS), - period_start.isoformat(' '),\ - period_stop.isoformat(' '), - tenant_clause)).fetchall() - - return rows - def _hours_for(self, instance, period_start, period_stop): launched_at = instance['launched_at'] terminated_at = instance['terminated_at'] @@ -99,62 +71,58 @@ class SimpleTenantUsageController(object): def _tenant_usages_for_period(self, context, period_start, period_stop, tenant_id=None, detailed=True): - rows = self._get_instances_for_time_period(period_start, - period_stop, - tenant_id) + instances = db.instance_get_active_by_window(context, + period_start, + period_stop, + tenant_id, + fast=True) + from nova import log as logging + logging.info(instances) rval = {} flavors = {} - for row in rows: + for instance in instances: info = {} - for i in range(len(INSTANCE_FIELDS)): - info[INSTANCE_FIELDS[i]] = row[i] - info['hours'] = self._hours_for(info, period_start, period_stop) - flavor_type = info['instance_type_id'] + info['hours'] = self._hours_for(instance, + period_start, + period_stop) + flavor_type = instance['instance_type_id'] if not flavors.get(flavor_type): try: flavors[flavor_type] = db.instance_type_get(context, - info['instance_type_id']) + flavor_type) except exception.InstanceTypeNotFound: # can't bill if there is no instance type continue flavor = flavors[flavor_type] - info['name'] = info['display_name'] - del(info['display_name']) + info['name'] = instance['display_name'] info['memory_mb'] = flavor['memory_mb'] info['local_gb'] = flavor['local_gb'] info['vcpus'] = flavor['vcpus'] - info['tenant_id'] = info['project_id'] - del(info['project_id']) + info['tenant_id'] = instance['project_id'] info['flavor'] = flavor['name'] - del(info['instance_type_id']) - info['started_at'] = info['launched_at'] - del(info['launched_at']) + info['started_at'] = instance['launched_at'] - info['ended_at'] = info['terminated_at'] - del(info['terminated_at']) + info['ended_at'] = instance['terminated_at'] if info['ended_at']: info['state'] = 'terminated' else: - info['state'] = info['state_description'] - - del(info['state_description']) + info['state'] = instance['state_description'] now = datetime.utcnow() if info['state'] == 'terminated': - delta = self._parse_datetime(info['ended_at'])\ - - self._parse_datetime(info['started_at']) + delta = info['ended_at'] - info['started_at'] else: - delta = now - self._parse_datetime(info['started_at']) + delta = now - info['started_at'] info['uptime'] = delta.days * 24 * 60 + delta.seconds -- cgit From 19cb3672f2849fe659173631f7f81ed489f1ea7e Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 26 Aug 2011 17:46:47 -0700 Subject: v1.0 of server create injects first users keypair --- nova/api/openstack/create_instance_helper.py | 1 - nova/api/openstack/servers.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index e7428bf41..d82cb534f 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -19,7 +19,6 @@ import base64 from webob import exc from xml.dom import minidom -from nova import db from nova import exception from nova import flags from nova import log as logging diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ad563b771..f288f2228 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -22,6 +22,7 @@ from xml.dom import minidom import webob from nova import compute +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -563,6 +564,18 @@ class ControllerV10(Controller): raise exc.HTTPNotFound() return webob.Response(status_int=202) + def create(self, req, body): + """ Creates a new server for a given user """ + # note(ja): v1.0 injects the first keypair for the project for testing + if 'server' in body and not 'key_name' in body['server']: + context = req.environ["nova.context"] + keypairs = db.key_pair_get_all_by_user(context.elevated(), + context.user_id) + if keypairs: + body['server']['key_name'] = keypairs[0]['name'] + + return super(ControllerV10, self).create(req, body) + def _image_ref_from_req_data(self, data): return data['server']['imageId'] -- cgit From 6b6de435efb83fc88c885c459fb70c46c646be84 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 28 Aug 2011 22:02:43 -0400 Subject: Update the EC2 ToToken middleware to use eventlet.green.httplib instead of httplib2. Fixes issues where the JSON request body wasn't getting sent to Keystone. --- nova/api/ec2/__init__.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5430f443d..ec4743cea 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -20,7 +20,10 @@ Starting point for routing EC2 requests. """ -import httplib2 +from urlparse import urlparse + +import eventlet +from eventlet.green import httplib import webob import webob.dec import webob.exc @@ -35,7 +38,6 @@ from nova.api.ec2 import apirequest from nova.api.ec2 import ec2utils from nova.auth import manager - FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api") flags.DEFINE_integer('lockout_attempts', 5, @@ -158,7 +160,6 @@ class ToToken(wsgi.Middleware): auth_params.pop('Signature') # Authenticate the request. - client = httplib2.Http() creds = {'ec2Credentials': {'access': access, 'signature': signature, 'host': req.host, @@ -166,18 +167,24 @@ class ToToken(wsgi.Middleware): 'path': req.path, 'params': auth_params, }} - headers = {'Content-Type': 'application/json'}, - resp, content = client.request(FLAGS.keystone_ec2_url, - 'POST', - headers=headers, - body=utils.dumps(creds)) + creds_json = utils.dumps(creds) + headers = {'Content-Type': 'application/json'} + o = urlparse(FLAGS.keystone_ec2_url) + if o.scheme == "http": + conn = httplib.HTTPConnection(o.netloc) + else: + conn = httplib.HTTPSConnection(o.netloc) + conn.request('POST', o.path, body=creds_json, headers=headers) + response = conn.getresponse().read() + conn.close() + # NOTE(vish): We could save a call to keystone by # having keystone return token, tenant, # user, and roles from this call. - result = utils.loads(content) + result = utils.loads(response) # TODO(vish): check for errors - token_id = result['auth']['token']['id'] + token_id = result['auth']['token']['id'] # Authenticated! req.headers['X-Auth-Token'] = token_id return self.application -- cgit From 1155b734164eb5856d68c926f7bf64a37ae4a3a4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 30 Aug 2011 11:13:25 -0400 Subject: supporting changes-since --- nova/api/openstack/servers.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 27c67e79e..e0e40679a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,6 +107,14 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) + if 'changes-since' in search_opts: + try: + parsed = utils.parse_isotime(search_opts['changes-since']) + except ValueError: + msg = _('Invalid changes-since value') + raise exc.HTTPBadRequest(explanation=msg) + search_opts['changes-since'] = parsed + # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. @@ -114,23 +122,17 @@ class Controller(object): # should return recently deleted images according to the API spec. if 'deleted' not in search_opts: - # Admin hasn't specified deleted filter if 'changes-since' not in search_opts: - # No 'changes-since', so we need to find non-deleted servers + # No 'changes-since', so we only want non-deleted servers search_opts['deleted'] = False - else: - # This is the default, but just in case.. - search_opts['deleted'] = True - - instance_list = self.compute_api.get_all( - context, search_opts=search_opts) - # FIXME(comstud): 'changes-since' is not fully implemented. Where - # should this be filtered? + instance_list = self.compute_api.get_all(context, + search_opts=search_opts) limited_list = self._limit_items(instance_list, req) servers = [self._build_view(req, inst, is_detail)['server'] - for inst in limited_list] + for inst in limited_list] + return dict(servers=servers) @scheduler_api.redirect_handler -- cgit From dcf5970dd9bed27201c593d7d053970a632e5eee Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:01:18 -0700 Subject: make two functions instead of fast flag and add compute api commands instead of hitting db directly --- nova/api/openstack/contrib/simple_tenant_usage.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 16e712815..363ac1451 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -19,10 +19,9 @@ import urlparse import webob from datetime import datetime -from nova import db from nova import exception from nova import flags -from nova.compute import instance_types +from nova.compute import api from nova.api.openstack import extensions from nova.api.openstack import views from nova.db.sqlalchemy.session import get_session @@ -71,11 +70,11 @@ class SimpleTenantUsageController(object): def _tenant_usages_for_period(self, context, period_start, period_stop, tenant_id=None, detailed=True): - instances = db.instance_get_active_by_window(context, + compute_api = api.API() + instances = compute_api.get_active_by_window(context, period_start, period_stop, - tenant_id, - fast=True) + tenant_id) from nova import log as logging logging.info(instances) rval = {} @@ -90,8 +89,9 @@ class SimpleTenantUsageController(object): if not flavors.get(flavor_type): try: - flavors[flavor_type] = db.instance_type_get(context, - flavor_type) + it_ref = compute_api.get_instance_type(context, + flavor_type) + flavors[flavor_type] = it_ref except exception.InstanceTypeNotFound: # can't bill if there is no instance type continue -- cgit From 2fcc6da8ba528c5169f7394d57f90ccd2754a23c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:14:25 -0700 Subject: pep8, fix fakes --- nova/api/openstack/contrib/simple_tenant_usage.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 363ac1451..e81aef66e 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -30,6 +30,7 @@ from webob import exc FLAGS = flags.FLAGS + class SimpleTenantUsageController(object): def _hours_for(self, instance, period_start, period_stop): launched_at = instance['launched_at'] -- cgit From 5cf27b5a338f7821f82c91df5889159b56fa0bb6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 30 Aug 2011 12:41:30 -0700 Subject: fix remaining tests --- nova/api/openstack/contrib/simple_tenant_usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index e81aef66e..69b38e229 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -212,7 +212,7 @@ class SimpleTenantUsageController(object): class Simple_tenant_usage(extensions.ExtensionDescriptor): def get_name(self): - return "Simple_tenant_usage" + return "SimpleTenantUsage" def get_alias(self): return "os-simple-tenant-usage" -- cgit From e326acf46748904704dd97f511927559dc2480f2 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 30 Aug 2011 15:05:39 -0700 Subject: Fix for LP Bug #837534 --- nova/api/openstack/create_instance_helper.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..019283fdf 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -138,7 +138,10 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) zone_blob = server_dict.get('blob') + user_data = server_dict.get('user_data') + self._validate_user_data(user_data) + availability_zone = server_dict.get('availability_zone') name = server_dict['name'] self._validate_server_name(name) @@ -370,6 +373,16 @@ class CreateInstanceHelper(object): return networks + def _validate_user_data(self, user_data): + """Check if the user_data is encoded properly""" + if not user_data: + return + try: + user_data = base64.b64decode(user_data) + except TypeError: + expl = _('Userdata content cannot be decoded') + raise exc.HTTPBadRequest(explanation=expl) + class ServerXMLDeserializer(wsgi.XMLDeserializer): """ -- cgit From 66aa9a6306cde5db2039daaf11a8422619560a33 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 30 Aug 2011 20:45:51 -0700 Subject: fix FloatingIpAlreadyInUse to use correct string pattern, convert ApiErrors to 400 responses --- nova/api/openstack/contrib/floating_ips.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 40086f778..ad3094d52 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -138,7 +138,11 @@ class Floating_ips(extensions.ExtensionDescriptor): msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) - self.compute_api.associate_floating_ip(context, instance_id, address) + try: + self.compute_api.associate_floating_ip(context, instance_id, + address) + except exception.ApiError, e: + raise webob.exc.HTTPBadRequest(explanation=e.message) return webob.Response(status_int=202) -- cgit From 1477b8c33374db1166c6c67ff68e03c94f3436a5 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 30 Aug 2011 21:16:26 -0700 Subject: add test to verify 400 response when out of addresses --- nova/api/openstack/contrib/floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index ad3094d52..99c0d1469 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -96,7 +96,7 @@ class FloatingIPController(object): except rpc.RemoteError as ex: # NOTE(tr3buchet) - why does this block exist? if ex.exc_type == 'NoMoreFloatingIps': - raise exception.NoMoreFloatingIps() + raise webob.exc.HTTPBadRequest(explanation=ex.message) else: raise -- cgit From abd6b240b5247a2981e86c1d3161306fb2b4c4c5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 12:02:14 -0700 Subject: moved key_name per review --- nova/api/openstack/views/servers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 35821f9a7..cd01c9373 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -79,8 +79,6 @@ class ViewBuilder(object): metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata - inst_dict['key_name'] = inst.get('key_name', '') - inst_dict['hostId'] = '' if inst.get('host'): inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() @@ -190,6 +188,7 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] + response['key_name'] = inst.get('key_name', '') self._build_config_drive(response, inst) def _build_links(self, response, inst): -- cgit From f687e978a41c78e10e0c371c5486298925b5857f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 12:44:15 -0700 Subject: add explicit message for NoMoreFloatingIps exception --- nova/api/openstack/contrib/floating_ips.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 99c0d1469..6ce531c8f 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -96,7 +96,8 @@ class FloatingIPController(object): except rpc.RemoteError as ex: # NOTE(tr3buchet) - why does this block exist? if ex.exc_type == 'NoMoreFloatingIps': - raise webob.exc.HTTPBadRequest(explanation=ex.message) + msg = _("No more floating ips available.") + raise webob.exc.HTTPBadRequest(explanation=msg) else: raise -- cgit From c9758dd4832c167562baefad5dcc88f2a1a19b73 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 12:51:16 -0700 Subject: fix keypairs stubs --- nova/api/openstack/create_instance_helper.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index d82cb534f..744353f31 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -189,6 +189,7 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) except exception.KeypairNotFound as error: msg = _("Invalid key_name provided.") + raise exc.HTTPBadRequest(explanation=msg) except exception.SecurityGroupNotFound as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except RemoteError as err: -- cgit From 9de8a589b4ee0e007267efe2394b504382e4cdc1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 31 Aug 2011 14:58:55 -0700 Subject: change to use _get_key_name to retrieve the key --- nova/api/openstack/servers.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f288f2228..53684fa52 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -144,8 +144,15 @@ class Controller(object): except exception.NotFound: raise exc.HTTPNotFound() + def _get_key_name(self, req, body): + """ Get default keypair if not set """ + raise NotImplementedError() + def create(self, req, body): """ Creates a new server for a given user """ + if 'server' in body: + body['server']['key_name'] = self._get_key_name(req, body) + extra_values = None extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) @@ -564,17 +571,12 @@ class ControllerV10(Controller): raise exc.HTTPNotFound() return webob.Response(status_int=202) - def create(self, req, body): - """ Creates a new server for a given user """ - # note(ja): v1.0 injects the first keypair for the project for testing - if 'server' in body and not 'key_name' in body['server']: - context = req.environ["nova.context"] - keypairs = db.key_pair_get_all_by_user(context.elevated(), - context.user_id) - if keypairs: - body['server']['key_name'] = keypairs[0]['name'] - - return super(ControllerV10, self).create(req, body) + def _get_key_name(self, req, body): + context = req.environ["nova.context"] + keypairs = db.key_pair_get_all_by_user(context, + context.user_id) + if keypairs: + return keypairs[0]['name'] def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -647,6 +649,10 @@ class ControllerV11(Controller): except exception.NotFound: raise exc.HTTPNotFound() + def _get_key_name(self, req, body): + if 'server' in body: + return body['server'].get('key_name') + def _image_ref_from_req_data(self, data): try: return data['server']['imageRef'] -- cgit From fdf076a04e001b897d01b2a8c4a9e3c980ea8f94 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 21:34:10 -0700 Subject: fix for lp838583 - return instance_id for associated floating_ips, add test --- nova/api/openstack/contrib/floating_ips.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 6ce531c8f..d1add8f83 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -36,9 +36,9 @@ def _translate_floating_ip_view(floating_ip): result['fixed_ip'] = floating_ip['fixed_ip']['address'] except (TypeError, KeyError): result['fixed_ip'] = None - if 'instance' in floating_ip: - result['instance_id'] = floating_ip['instance']['id'] - else: + try: + result['instance_id'] = floating_ip['fixed_ip']['instance_id'] + except (TypeError, KeyError): result['instance_id'] = None return {'floating_ip': result} -- cgit From 59be9be68c0fd9b33b72257b8a1eb8c357ce9217 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 1 Sep 2011 12:22:32 -0700 Subject: remove extra references to state_description --- nova/api/ec2/admin.py | 5 ++--- nova/api/openstack/contrib/simple_tenant_usage.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index dfbbc0a2b..75e029509 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -21,7 +21,6 @@ Admin API controller, exposed through http via the api worker. """ import base64 -import datetime import netaddr import urllib @@ -33,6 +32,7 @@ from nova import log as logging from nova import utils from nova.api.ec2 import ec2utils from nova.auth import manager +from nova.compute import vm_states FLAGS = flags.FLAGS @@ -273,8 +273,7 @@ class AdminController(object): """Get the VPN instance for a project ID.""" for instance in db.instance_get_all_by_project(context, project_id): if (instance['image_id'] == str(FLAGS.vpn_image_id) - and not instance['state_description'] in - ['shutting_down', 'shutdown']): + and not instance['vm_state'] in [vm_states.DELETED]): return instance def start_vpn(self, context, project): diff --git a/nova/api/openstack/contrib/simple_tenant_usage.py b/nova/api/openstack/contrib/simple_tenant_usage.py index 69b38e229..42691a9fa 100644 --- a/nova/api/openstack/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/contrib/simple_tenant_usage.py @@ -116,7 +116,7 @@ class SimpleTenantUsageController(object): if info['ended_at']: info['state'] = 'terminated' else: - info['state'] = instance['state_description'] + info['state'] = instance['vm_state'] now = datetime.utcnow() -- cgit