diff options
| author | Cerberus <matt.dietz@rackspace.com> | 2010-09-29 17:24:36 -0500 |
|---|---|---|
| committer | Cerberus <matt.dietz@rackspace.com> | 2010-09-29 17:24:36 -0500 |
| commit | 0030da605c30bbf3b1424aae86bbdc07ff7c50c2 (patch) | |
| tree | 5d7498de30ebc00ba43221a07f204a98984dab8e /nova/api | |
| parent | 35741ff23bec2b4f301b93128fd018e9c8e70945 (diff) | |
| parent | 5654c7848048ecad0aef020b96001aed3e5c1bdc (diff) | |
Merge from trunk and networking setup for new instances
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/ec2/__init__.py | 2 | ||||
| -rw-r--r-- | nova/api/ec2/cloud.py | 58 | ||||
| -rw-r--r-- | nova/api/ec2/images.py | 8 | ||||
| -rw-r--r-- | nova/api/rackspace/__init__.py | 29 | ||||
| -rw-r--r-- | nova/api/rackspace/auth.py | 7 | ||||
| -rw-r--r-- | nova/api/rackspace/backup_schedules.py | 7 | ||||
| -rw-r--r-- | nova/api/rackspace/faults.py | 62 | ||||
| -rw-r--r-- | nova/api/rackspace/flavors.py | 8 | ||||
| -rw-r--r-- | nova/api/rackspace/images.py | 9 | ||||
| -rw-r--r-- | nova/api/rackspace/servers.py | 43 |
10 files changed, 191 insertions, 42 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index f0aa57ee4..7a958f841 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -158,12 +158,14 @@ class Authorizer(wsgi.Middleware): 'RunInstances': ['projectmanager', 'sysadmin'], 'TerminateInstances': ['projectmanager', 'sysadmin'], 'RebootInstances': ['projectmanager', 'sysadmin'], + 'UpdateInstance': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'], 'DescribeImages': ['all'], 'DeregisterImage': ['projectmanager', 'sysadmin'], 'RegisterImage': ['projectmanager', 'sysadmin'], 'DescribeImageAttribute': ['all'], 'ModifyImageAttribute': ['projectmanager', 'sysadmin'], + 'UpdateImage': ['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 4d962fcdd..79c95788b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -102,7 +102,7 @@ class CloudController(object): def _get_mpi_data(self, project_id): result = {} - for instance in db.instance_get_by_project(None, project_id): + for instance in db.instance_get_all_by_project(None, project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['address'], INSTANCE_TYPES[instance['instance_type']]['vcpus']) @@ -257,7 +257,7 @@ class CloudController(object): if context.user.is_admin(): volumes = db.volume_get_all(context) else: - volumes = db.volume_get_by_project(context, context.project.id) + volumes = db.volume_get_all_by_project(context, context.project.id) volumes = [self._format_volume(context, v) for v in volumes] @@ -286,6 +286,9 @@ class CloudController(object): 'volume_id': volume['ec2_id']}] else: v['attachmentSet'] = [{}] + + v['display_name'] = volume['display_name'] + v['display_description'] = volume['display_description'] return v def create_volume(self, context, size, **kwargs): @@ -303,6 +306,8 @@ class CloudController(object): vol['availability_zone'] = FLAGS.storage_availability_zone vol['status'] = "creating" vol['attach_status'] = "detached" + vol['display_name'] = kwargs.get('display_name') + vol['display_description'] = kwargs.get('display_description') volume_ref = db.volume_create(context, vol) rpc.cast(FLAGS.scheduler_topic, @@ -369,6 +374,16 @@ class CloudController(object): lst = [lst] return [{label: x} for x in lst] + def update_volume(self, context, volume_id, **kwargs): + updatable_fields = ['display_name', 'display_description'] + changes = {} + for field in updatable_fields: + if field in kwargs: + changes[field] = kwargs[field] + if changes: + db.volume_update(context, volume_id, kwargs) + return True + def describe_instances(self, context, **kwargs): return self._format_describe_instances(context) @@ -383,14 +398,14 @@ class CloudController(object): def _format_instances(self, context, reservation_id=None): reservations = {} if reservation_id: - instances = db.instance_get_by_reservation(context, - reservation_id) + instances = db.instance_get_all_by_reservation(context, + reservation_id) else: if context.user.is_admin(): instances = db.instance_get_all(context) else: - instances = db.instance_get_by_project(context, - context.project.id) + instances = db.instance_get_all_by_project(context, + context.project.id) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: @@ -421,6 +436,8 @@ class CloudController(object): i['instanceType'] = instance['instance_type'] i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] + i['displayName'] = instance['display_name'] + i['displayDescription'] = instance['display_description'] if not reservations.has_key(instance['reservation_id']): r = {} r['reservationId'] = instance['reservation_id'] @@ -440,8 +457,8 @@ class CloudController(object): if context.user.is_admin(): iterator = db.floating_ip_get_all(context) else: - iterator = db.floating_ip_get_by_project(context, - context.project.id) + iterator = db.floating_ip_get_all_by_project(context, + context.project.id) for floating_ip_ref in iterator: address = floating_ip_ref['address'] instance_id = None @@ -483,14 +500,15 @@ class CloudController(object): def associate_address(self, context, instance_id, public_ip, **kwargs): instance_ref = db.instance_get_by_ec2_id(context, instance_id) - fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['id']) + fixed_address = db.instance_get_fixed_address(context, + instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) network_topic = self._get_network_topic(context) rpc.cast(network_topic, {"method": "associate_floating_ip", "args": {"context": None, "floating_address": floating_ip_ref['address'], - "fixed_address": fixed_ip_ref['address']}}) + "fixed_address": fixed_address}}) return {'associateResponse': ["Address associated."]} def disassociate_address(self, context, public_ip, **kwargs): @@ -577,6 +595,8 @@ class CloudController(object): base_options['user_data'] = kwargs.get('user_data', '') base_options['security_group'] = security_group base_options['instance_type'] = instance_type + base_options['display_name'] = kwargs.get('display_name') + base_options['display_description'] = kwargs.get('display_description') type_data = INSTANCE_TYPES[instance_type] base_options['memory_mb'] = type_data['memory_mb'] @@ -641,7 +661,7 @@ class CloudController(object): rpc.cast(network_topic, {"method": "disassociate_floating_ip", "args": {"context": None, - "address": address}}) + "floating_address": address}}) address = db.instance_get_fixed_address(context, instance_ref['id']) @@ -668,6 +688,18 @@ class CloudController(object): cloud.reboot(id_str, context=context) return True + def update_instance(self, context, instance_id, **kwargs): + updatable_fields = ['display_name', 'display_description'] + changes = {} + for field in updatable_fields: + if field in kwargs: + changes[field] = kwargs[field] + if changes: + db_context = {} + inst = db.instance_get_by_ec2_id(db_context, instance_id) + db.instance_update(db_context, inst['id'], kwargs) + return True + def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized volume_ref = db.volume_get_by_ec2_id(context, volume_id) @@ -723,3 +755,7 @@ class CloudController(object): if not operation_type in ['add', 'remove']: raise exception.ApiError('operation_type must be add or remove') return images.modify(context, image_id, operation_type) + + def update_image(self, context, image_id, **kwargs): + result = images.update(context, image_id, dict(kwargs)) + return result diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py index 4579cd81a..cb54cdda2 100644 --- a/nova/api/ec2/images.py +++ b/nova/api/ec2/images.py @@ -43,6 +43,14 @@ def modify(context, image_id, operation): return True +def update(context, image_id, attributes): + """update an image's attributes / info.json""" + attributes.update({"image_id": image_id}) + conn(context).make_request( + method='POST', + bucket='_images', + query_args=qs(attributes)) + return True def register(context, image_location): """ rpc call to register a new image based from a manifest """ diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 98802663f..89a4693ad 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -31,6 +31,7 @@ import webob from nova import flags from nova import utils from nova import wsgi +from nova.api.rackspace import faults from nova.api.rackspace import backup_schedules from nova.api.rackspace import flavors from nova.api.rackspace import images @@ -67,7 +68,7 @@ class AuthMiddleware(wsgi.Middleware): user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) if not user: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) if not req.environ.has_key('nova.context'): req.environ['nova.context'] = {} @@ -112,8 +113,10 @@ class RateLimitingMiddleware(wsgi.Middleware): delay = self.get_delay(action_name, username) if delay: # TODO(gundlach): Get the retry-after format correct. - raise webob.exc.HTTPRequestEntityTooLarge(headers={ - 'Retry-After': time.time() + delay}) + exc = webob.exc.HTTPRequestEntityTooLarge( + explanation='Too many requests.', + headers={'Retry-After': time.time() + delay}) + raise faults.Fault(exc) return self.application def get_delay(self, action_name, username): @@ -165,3 +168,23 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) + + +def limited(items, req): + """Return a slice of items according to requested offset and limit. + + items - a sliceable + req - wobob.Request possibly containing offset and limit GET variables. + offset is where to start in the list, and limit is the maximum number + of items to return. + + If limit is not specified, 0, or > 1000, defaults to 1000. + """ + offset = int(req.GET.get('offset', 0)) + limit = int(req.GET.get('limit', 0)) + if not limit: + limit = 1000 + limit = min(1000, limit) + range_end = offset + limit + return items[offset:range_end] + diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index 8bfb0753e..c45156ebd 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -11,6 +11,7 @@ from nova import db from nova import flags from nova import manager from nova import utils +from nova.api.rackspace import faults FLAGS = flags.FLAGS @@ -36,13 +37,13 @@ class BasicApiAuthManager(object): # honor it path_info = req.path_info if len(path_info) > 1: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) try: username, key = req.headers['X-Auth-User'], \ req.headers['X-Auth-Key'] except KeyError: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] token, user = self._authorize_user(username, key) @@ -57,7 +58,7 @@ class BasicApiAuthManager(object): res.status = '204' return res else: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) def authorize_token(self, token_hash): """ retrieves user information from the datastore given a token diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/rackspace/backup_schedules.py index 46da778ee..cb83023bc 100644 --- a/nova/api/rackspace/backup_schedules.py +++ b/nova/api/rackspace/backup_schedules.py @@ -20,6 +20,7 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults import nova.image.service class Controller(wsgi.Controller): @@ -27,12 +28,12 @@ class Controller(wsgi.Controller): pass def index(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def create(self, req, server_id): """ No actual update method required, since the existing API allows both create and update through a POST """ - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def delete(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/rackspace/faults.py b/nova/api/rackspace/faults.py new file mode 100644 index 000000000..32e5c866f --- /dev/null +++ b/nova/api/rackspace/faults.py @@ -0,0 +1,62 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import webob.dec +import webob.exc + +from nova import wsgi + + +class Fault(webob.exc.HTTPException): + + """An RS API fault response.""" + + _fault_names = { + 400: "badRequest", + 401: "unauthorized", + 403: "resizeNotAllowed", + 404: "itemNotFound", + 405: "badMethod", + 409: "inProgress", + 413: "overLimit", + 415: "badMediaType", + 501: "notImplemented", + 503: "serviceUnavailable"} + + def __init__(self, exception): + """Create a Fault for the given webob.exc.exception.""" + self.wrapped_exc = exception + + @webob.dec.wsgify + def __call__(self, req): + """Generate a WSGI response based on the exception passed to ctor.""" + # Replace the body with fault details. + code = self.wrapped_exc.status_int + fault_name = self._fault_names.get(code, "cloudServersFault") + fault_data = { + fault_name: { + 'code': code, + 'message': self.wrapped_exc.explanation}} + if code == 413: + retry = self.wrapped_exc.headers['Retry-After'] + fault_data[fault_name]['retryAfter'] = retry + # 'code' is an attribute on the fault tag itself + metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} + serializer = wsgi.Serializer(req.environ, metadata) + self.wrapped_exc.body = serializer.to_content_type(fault_data) + return self.wrapped_exc diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 3bcf170e5..916449854 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -15,9 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +from webob import exc + +from nova.api.rackspace import faults from nova.compute import instance_types from nova import wsgi -from webob import exc +import nova.api.rackspace class Controller(wsgi.Controller): """Flavor controller for the Rackspace API.""" @@ -38,6 +41,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all flavors in detail.""" items = [self.show(req, id)['flavor'] for id in self._all_ids()] + items = nova.api.rackspace.limited(items, req) return dict(flavors=items) def show(self, req, id): @@ -47,7 +51,7 @@ class Controller(wsgi.Controller): item = dict(ram=val['memory_mb'], disk=val['local_gb'], id=val['flavorid'], name=name) return dict(flavor=item) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def _all_ids(self): """Return the list of all flavorids.""" diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 11b058dec..4a7dd489c 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -19,7 +19,9 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +import nova.api.rackspace import nova.image.service +from nova.api.rackspace import faults class Controller(wsgi.Controller): @@ -45,6 +47,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" data = self._service.index() + data = nova.api.rackspace.limited(data, req) for img in data: img['id'] = self._id_translator.to_rs_id(img['id']) return dict(images=data) @@ -58,14 +61,14 @@ class Controller(wsgi.Controller): def delete(self, req, id): # Only public images are supported for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def create(self, req): # Only public images are supported for now, so a request to # make a backup of a server cannot be supproted. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def update(self, req, id): # Users may not modify public images, and that's all that # we support for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 40cd4f691..c3d56debd 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -18,6 +18,7 @@ import datetime import time +import webob from webob import exc from nova import flags @@ -31,6 +32,7 @@ from nova.api.rackspace import faults from nova.compute import instance_types from nova.compute import power_state from nova.wsgi import Serializer +import nova.api.rackspace import nova.image.service FLAGS = flags.FLAGS @@ -112,16 +114,21 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - user_id = req.environ['nova.context']['user']['id'] - instance_list = self.db_driver.instance_get_all_by_user(None, user_id) - res = [_entity_inst(inst)['server'] for inst in instance_list] - return _entity_list(res) + return self._items(req, entity_maker=_entity_inst) def detail(self, req): """ Returns a list of server details for a given user """ + return self._items(req, entity_maker=_entity_detail) + + def _items(self, req, entity_maker): + """Returns a list of servers for a given user. + + entity_maker - either _entity_detail or _entity_inst + """ user_id = req.environ['nova.context']['user']['id'] - res = [_entity_detail(inst)['server'] for inst in - self.db_driver.instance_get_all_by_user(None, user_id)] + instance_list = self.db_driver.instance_get_all_by_user(None, user_id) + limited_list = nova.api.rackspace.limited(instance_list, req) + res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) def show(self, req, id): @@ -134,7 +141,7 @@ class Controller(wsgi.Controller): if inst: if inst.user_id == user_id: return _entity_detail(inst) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def delete(self, req, id): """ Destroys a server """ @@ -145,15 +152,15 @@ class Controller(wsgi.Controller): instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) if instance and instance['user_id'] == user_id: self.db_driver.instance_destroy(None, id) - return exc.HTTPAccepted() - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPAccepted()) + return faults.Fault(exc.HTTPNotFound()) def create(self, req): """ Creates a new server for a given user """ env = self._deserialize(req.body, req) if not env: - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) #try: inst = self._build_server_instance(req, env) @@ -176,15 +183,15 @@ class Controller(wsgi.Controller): inst_dict = self._deserialize(req.body, req) if not inst_dict: - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) if not instance or instance.user_id != user_id: - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) self.db_driver.instance_update(None, id, _filter_params(inst_dict['server'])) - return exc.HTTPNoContent() + return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and @@ -259,17 +266,19 @@ class Controller(wsgi.Controller): # TODO(dietz): this isn't explicitly necessary, but the networking # calls depend on an object with a project_id property - context = context.APIRequestContext(user_id) + api_context = context.APIRequestContext(user_id) inst['mac_address'] = utils.generate_mac() #TODO(dietz) is this necessary? inst['launch_index'] = 0 - inst['hostname'] = instance_ref['ec2_id'] - self.db_driver.instance_update(None, inst_id, inst) + inst['hostname'] = ref.ec2_id + self.db_driver.instance_update(None, inst['id'], inst) + self.network_manager = utils.import_object(FLAGS.rs_network_manager) - address = self.network_manager.allocate_fixed_ip(context, inst_id) + address = self.network_manager.allocate_fixed_ip(api_context, + inst['id']) # TODO(vish): This probably should be done in the scheduler # network is setup when host is assigned |
