From 04b50db56ee90c0f4dd685a8f45883522260164f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 11 Jul 2011 14:27:01 -0700 Subject: Replace 'like' support with 'regexp' matching done in python. Since 'like' would result in a full table scan anyway, this is a bit more flexible. Make search options and matching a little more generic Return 404 when --fixed_ip doesn't match any instance, instead of a 500 only when the IP isn't in the FixedIps table. --- nova/api/ec2/cloud.py | 23 ++++++++++++++++++----- nova/api/openstack/servers.py | 20 +++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9be30cf75..9efbb5985 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -118,8 +118,9 @@ class CloudController(object): def _get_mpi_data(self, context, project_id): result = {} + search_opts = {'project_id': project_id} for instance in self.compute_api.get_all(context, - project_id=project_id): + search_opts=search_opts): if instance['fixed_ips']: line = '%s slots=%d' % (instance['fixed_ips'][0]['address'], instance['vcpus']) @@ -145,7 +146,12 @@ class CloudController(object): def get_metadata(self, address): ctxt = context.get_admin_context() - instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) + search_opts = {'fixed_ip': address} + try: + instance_ref = self.compute_api.get_all(ctxt, + search_opts=search_opts) + except exception.NotFound: + instance_ref = None if instance_ref is None: return None @@ -816,11 +822,18 @@ class CloudController(object): instances = [] for ec2_id in instance_id: internal_id = ec2utils.ec2_id_to_id(ec2_id) - instance = self.compute_api.get(context, - instance_id=internal_id) + try: + instance = self.compute_api.get(context, + instance_id=internal_id) + except exception.NotFound: + continue instances.append(instance) else: - instances = self.compute_api.get_all(context, **kwargs) + try: + instances = self.compute_api.get_all(context, + search_opts=kwargs) + except exception.NotFound: + instances = [] for instance in instances: if not context.is_admin: if instance['image_ref'] == str(FLAGS.vpn_image_id): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc1ab8d46..d259590a5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -52,6 +52,8 @@ class Controller(object): servers = self._items(req, is_detail=False) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) + except exception.NotFound: + return exc.HTTPNotFound() return servers def detail(self, req): @@ -60,6 +62,8 @@ class Controller(object): servers = self._items(req, is_detail=True) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) + except exception.NotFound as err: + return exc.HTTPNotFound() return servers def _get_view_builder(self, req): @@ -77,16 +81,14 @@ class Controller(object): builder - the response model builder """ query_str = req.str_GET - reservation_id = query_str.get('reservation_id') - project_id = query_str.get('project_id') - fixed_ip = query_str.get('fixed_ip') - recurse_zones = utils.bool_from_str(query_str.get('recurse_zones')) + recurse_zones = utils.bool_from_str( + query_str.get('recurse_zones', False)) + # Pass all of the options on to compute's 'get_all' + search_opts = query_str + # Reset this after converting from string to bool + search_opts['recurse_zones'] = recurse_zones instance_list = self.compute_api.get_all( - req.environ['nova.context'], - reservation_id=reservation_id, - project_id=project_id, - fixed_ip=fixed_ip, - recurse_zones=recurse_zones) + req.environ['nova.context'], search_opts=search_opts) limited_list = self._limit_items(instance_list, req) builder = self._get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] -- cgit From d2265cbe65f1b3940b37966245da13b9714234ef Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 17 Jul 2011 16:12:59 -0700 Subject: Refactored OS API code to allow checking of invalid query string paremeters and admin api/context to the index/detail calls. v1.0 still ignores unknown parameters, but v1.1 will return 400/BadRequest on unknown options. admin_api only commands are treated as unknown parameters if FLAGS.enable_admin_api is False. If enable_admin_api is True, non-admin context requests return 403/Forbidden. Fixed EC2 API code to handle search options to compute_api.get_all() more correctly. Reverted compute_api.get_all to ignore unknown options, since the OS API now does the verification. Updated tests. --- nova/api/ec2/cloud.py | 23 +++++--- nova/api/openstack/servers.py | 123 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 124 insertions(+), 22 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0d24f0938..76725370a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -800,11 +800,16 @@ class CloudController(object): return [{label: x} for x in lst] def describe_instances(self, context, **kwargs): - return self._format_describe_instances(context, **kwargs) + # Optional DescribeInstances argument + instance_id = kwargs.get('instance_id', None) + return self._format_describe_instances(context, + instance_id=instance_id) def describe_instances_v6(self, context, **kwargs): - kwargs['use_v6'] = True - return self._format_describe_instances(context, **kwargs) + # Optional DescribeInstancesV6 argument + instance_id = kwargs.get('instance_id', None) + return self._format_describe_instances(context, + instance_id=instance_id, use_v6=True) def _format_describe_instances(self, context, **kwargs): return {'reservationSet': self._format_instances(context, **kwargs)} @@ -814,7 +819,8 @@ class CloudController(object): assert len(i) == 1 return i[0] - def _format_instances(self, context, instance_id=None, **kwargs): + def _format_instances(self, context, instance_id=None, use_v6=False, + **search_opts): # TODO(termie): this method is poorly named as its name does not imply # that it will be making a variety of database calls # rather than simply formatting a bunch of instances that @@ -827,14 +833,15 @@ class CloudController(object): internal_id = ec2utils.ec2_id_to_id(ec2_id) try: instance = self.compute_api.get(context, - instance_id=internal_id) + instance_id=internal_id, + search_opts=search_opts) except exception.NotFound: continue instances.append(instance) else: try: instances = self.compute_api.get_all(context, - search_opts=kwargs) + search_opts=search_opts) except exception.NotFound: instances = [] for instance in instances: @@ -856,7 +863,7 @@ class CloudController(object): fixed_addr = fixed['address'] if fixed['floating_ips']: floating_addr = fixed['floating_ips'][0]['address'] - if fixed['network'] and 'use_v6' in kwargs: + if fixed['network'] and use_v6: i['dnsNameV6'] = ipv6.to_global( fixed['network']['cidr_v6'], fixed['virtual_interface']['address'], @@ -1014,7 +1021,7 @@ class CloudController(object): 'AvailabilityZone'), block_device_mapping=kwargs.get('block_device_mapping', {})) return self._format_run_instances(context, - instances[0]['reservation_id']) + instance_id=instances[0]['reservation_id']) def _do_instance(self, action, context, ec2_id): instance_id = ec2utils.ec2_id_to_id(ec2_id) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8a947c0e0..218037d14 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -39,6 +39,41 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS +def check_option_permissions(context, specified_options, + user_api_options, admin_api_options): + """Check whether or not entries in 'specified_options' are valid + based on the allowed 'user_api_options' and 'admin_api_options'. + + All inputs are lists of option names + + Returns: exception.InvalidInput for an invalid option or + exception.AdminRequired for needing admin privs + """ + + # We pretend we don't know about admin_api_options if the admin + # API is not enabled. + if FLAGS.enable_admin_api: + known_options = user_api_options + admin_api_options + else: + known_options = user_api_options + + # Check for unknown query string params. + spec_unknown_opts = [for opt in specified_options + if opt not in known_options] + if spec_unknown_opts: + unknown_opt_str = ", ".join(spec_unknown_opts) + raise exception.InvalidInput(reason=_( + "Unknown options specified: %(unknown_opt_str)")) + + # Check for admin context for the admin commands + if not context.is_admin: + spec_admin_opts = [for opt in specified_options + if opt in admin_api_options] + if spec_admin_opts: + admin_opt_str = ", ".join(admin_opts) + raise exception.AdminRequired() + + class Controller(object): """ The Server API controller for the OpenStack API """ @@ -51,9 +86,9 @@ class Controller(object): try: servers = self._items(req, is_detail=False) except exception.Invalid as err: - return exc.HTTPBadRequest(explanation=str(err)) + return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) except exception.NotFound: - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) return servers def detail(self, req): @@ -61,9 +96,9 @@ class Controller(object): try: servers = self._items(req, is_detail=True) except exception.Invalid as err: - return exc.HTTPBadRequest(explanation=str(err)) + return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) except exception.NotFound as err: - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) return servers def _get_view_builder(self, req): @@ -75,20 +110,17 @@ class Controller(object): def _action_rebuild(self, info, request, instance_id): raise NotImplementedError() - def _items(self, req, is_detail): - """Returns a list of servers for a given user. + def _get_items(self, context, req, is_detail, search_opts=None): + """Returns a list of servers. builder - the response model builder """ - query_str = req.str_GET - recurse_zones = utils.bool_from_str( - query_str.get('recurse_zones', False)) - # Pass all of the options on to compute's 'get_all' - search_opts = query_str - # Reset this after converting from string to bool - search_opts['recurse_zones'] = recurse_zones + + if search_opts is None: + search_opts = {} + instance_list = self.compute_api.get_all( - req.environ['nova.context'], search_opts=search_opts) + context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) builder = self._get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] @@ -422,6 +454,41 @@ class ControllerV10(Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + def _items(self, req, is_detail): + """Returns a list of servers based on the request. + + Checks for search options and permissions on the options. + """ + + search_opts = {} + search_opts.update(req.str_GET) + + user_api = ['project_id', 'fixed_ip', 'recurse_zones', + 'reservation_id', 'name', 'fresh', 'ip', 'ip6'] + admin_api = ['instance_name'] + + context = req.environ['nova.context'] + + try: + check_option_permissions(context, search_opt.keys(), + user_api, admin_api) + except exception.InvalidInput: + # FIXME(comstud): I refactored code in here to support + # new search options, and the original code ignored + # invalid options. So, I've left it this way for now. + # The v1.1 implementation will return an error in this + # case.. + pass + except exception.AdminRequired, e: + raise faults.Fault(exc.HTTPForbidden(detail=str(e))) + + # Convert recurse_zones into a boolean + search_opts['recurse_zones'] = utils.bool_from_str( + search_opts.get('recurse_zones', False)) + + return self._get_items(context, req, is_detail, + search_opts=search_opts) + def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -493,6 +560,34 @@ class ControllerV11(Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + def _items(self, req, is_detail): + """Returns a list of servers based on the request. + + Checks for search options and permissions on the options. + """ + + search_opts = {} + search_opts.update(req.str_GET) + + user_api = ['image', 'flavor', 'name', 'status', + 'reservation_id', 'changes-since', 'ip', 'ip6'] + admin_api = ['ip', 'ip6', 'instance_name'] + + context = req.environ['nova.context'] + + try: + check_option_permissions(context, search_opt.keys(), + user_api, admin_api) + except exception.InvalidInput, e: + raise faults.Fault(exc.HTTPBadRequest(detail=str(e))) + except exception.AdminRequired, e: + raise faults.Fault(exc.HTTPForbidden(detail=str(e))) + + # NOTE(comstud): Making recurse_zones always be True in v1.1 + search_opts['recurse_zones'] = True + return self._get_items(context, req, is_detail, + search_opts=search_opts) + def _image_ref_from_req_data(self, data): return data['server']['imageRef'] -- cgit From 102a0e5b9d6ce22a5fc5a00fc260bbe1e3592222 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 02:45:10 -0700 Subject: added searching by 'image', 'flavor', and 'status' reverted ip/ip6 searching to be admin only --- nova/api/openstack/servers.py | 18 +++++++++++++----- nova/api/openstack/views/servers.py | 15 +-------------- 2 files changed, 14 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 218037d14..fb1ce2529 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -119,6 +119,14 @@ class Controller(object): if search_opts is None: search_opts = {} + # If search by 'status', we need to convert it to 'state' + # If the status is unknown, bail + status = search_opts.pop('status', None) + if status is not None: + search_opts['state'] = power_state.states_from_status(status) + if len(search_opts['state']) == 0: + raise exception.InvalidInput(reason=_( + 'Invalid server status')) instance_list = self.compute_api.get_all( context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) @@ -464,8 +472,8 @@ class ControllerV10(Controller): search_opts.update(req.str_GET) user_api = ['project_id', 'fixed_ip', 'recurse_zones', - 'reservation_id', 'name', 'fresh', 'ip', 'ip6'] - admin_api = ['instance_name'] + 'reservation_id', 'name', 'fresh', 'status'] + admin_api = ['ip', 'ip6', 'instance_name'] context = req.environ['nova.context'] @@ -570,7 +578,7 @@ class ControllerV11(Controller): search_opts.update(req.str_GET) user_api = ['image', 'flavor', 'name', 'status', - 'reservation_id', 'changes-since', 'ip', 'ip6'] + 'reservation_id', 'changes-since'] admin_api = ['ip', 'ip6', 'instance_name'] context = req.environ['nova.context'] @@ -579,9 +587,9 @@ class ControllerV11(Controller): check_option_permissions(context, search_opt.keys(), user_api, admin_api) except exception.InvalidInput, e: - raise faults.Fault(exc.HTTPBadRequest(detail=str(e))) + raise faults.Fault(exc.HTTPBadRequest(explanation=str(e))) except exception.AdminRequired, e: - raise faults.Fault(exc.HTTPForbidden(detail=str(e))) + raise faults.Fault(exc.HTTPForbidden(explanation=str(e))) # NOTE(comstud): Making recurse_zones always be True in v1.1 search_opts['recurse_zones'] = True diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 67fb6a84e..1883ce2a5 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -60,25 +60,12 @@ class ViewBuilder(object): def _build_detail(self, inst): """Returns a detailed model of a server.""" - power_mapping = { - None: 'BUILD', - power_state.NOSTATE: 'BUILD', - power_state.RUNNING: 'ACTIVE', - power_state.BLOCKED: 'ACTIVE', - power_state.SUSPENDED: 'SUSPENDED', - power_state.PAUSED: 'PAUSED', - power_state.SHUTDOWN: 'SHUTDOWN', - power_state.SHUTOFF: 'SHUTOFF', - power_state.CRASHED: 'ERROR', - power_state.FAILED: 'ERROR', - power_state.BUILDING: 'BUILD', - } inst_dict = { 'id': inst['id'], 'name': inst['display_name'], 'addresses': self.addresses_builder.build(inst), - 'status': power_mapping[inst.get('state')]} + 'status': power_state.status_from_state(inst.get('state'))} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() -- cgit From 68ca0a6e770eadf1ed56aa9d0bef14c5ca16e172 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 02:49:42 -0700 Subject: add image and flavor searching to v1.0 api fixed missing updates from cut n paste in some doc strings --- nova/api/openstack/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fb1ce2529..b9347a014 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -472,7 +472,8 @@ class ControllerV10(Controller): search_opts.update(req.str_GET) user_api = ['project_id', 'fixed_ip', 'recurse_zones', - 'reservation_id', 'name', 'fresh', 'status'] + 'reservation_id', 'name', 'fresh', 'status', + 'image', 'flavor'] admin_api = ['ip', 'ip6', 'instance_name'] context = req.environ['nova.context'] -- cgit From a6968a100d2a2409094f7b434a88c700ebb876f3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 02:59:03 -0700 Subject: flavor needs to be converted to int from query string value --- nova/api/openstack/servers.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b9347a014..f470c59e3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -494,6 +494,9 @@ class ControllerV10(Controller): # Convert recurse_zones into a boolean search_opts['recurse_zones'] = utils.bool_from_str( search_opts.get('recurse_zones', False)) + # convert flavor into an int + if 'flavor' in search_opts: + search_opts['flavor'] = int(search_opts['flavor']) return self._get_items(context, req, is_detail, search_opts=search_opts) @@ -594,6 +597,9 @@ class ControllerV11(Controller): # NOTE(comstud): Making recurse_zones always be True in v1.1 search_opts['recurse_zones'] = True + # convert flavor into an int + if 'flavor' in search_opts: + search_opts['flavor'] = int(search_opts['flavor']) return self._get_items(context, req, is_detail, search_opts=search_opts) -- cgit From bfee5105a2e557a28a605778599e99308f2a126e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 03:02:50 -0700 Subject: typos --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f470c59e3..9bfcf585b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -58,7 +58,7 @@ def check_option_permissions(context, specified_options, known_options = user_api_options # Check for unknown query string params. - spec_unknown_opts = [for opt in specified_options + spec_unknown_opts = [opt for opt in specified_options if opt not in known_options] if spec_unknown_opts: unknown_opt_str = ", ".join(spec_unknown_opts) @@ -67,7 +67,7 @@ def check_option_permissions(context, specified_options, # Check for admin context for the admin commands if not context.is_admin: - spec_admin_opts = [for opt in specified_options + spec_admin_opts = [opt for opt in specified_options if opt in admin_api_options] if spec_admin_opts: admin_opt_str = ", ".join(admin_opts) -- cgit From edaeb96d6ce9c14b1f70a71c219d0353b59ed270 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 03:08:23 -0700 Subject: more typos --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9bfcf585b..771939624 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -52,7 +52,7 @@ def check_option_permissions(context, specified_options, # We pretend we don't know about admin_api_options if the admin # API is not enabled. - if FLAGS.enable_admin_api: + if FLAGS.allow_admin_api: known_options = user_api_options + admin_api_options else: known_options = user_api_options @@ -479,7 +479,7 @@ class ControllerV10(Controller): context = req.environ['nova.context'] try: - check_option_permissions(context, search_opt.keys(), + check_option_permissions(context, search_opts.keys(), user_api, admin_api) except exception.InvalidInput: # FIXME(comstud): I refactored code in here to support -- cgit From 043cfae7737a977f7f03d75910742f741b832323 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 03:36:08 -0700 Subject: missed power_state import in api fixed reversed compare in power_state --- nova/api/openstack/servers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 771939624..5a4dcbd9e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -32,6 +32,7 @@ import nova.api.openstack.views.images import nova.api.openstack.views.servers from nova.api.openstack import wsgi import nova.api.openstack +from nova.compute import power_state from nova.scheduler import api as scheduler_api @@ -125,8 +126,9 @@ class Controller(object): if status is not None: search_opts['state'] = power_state.states_from_status(status) if len(search_opts['state']) == 0: - raise exception.InvalidInput(reason=_( - 'Invalid server status')) + reason = _('Invalid server status: %(status)s') % locals() + LOG.error(reason) + raise exception.InvalidInput(reason=reason) instance_list = self.compute_api.get_all( context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) @@ -482,7 +484,7 @@ class ControllerV10(Controller): check_option_permissions(context, search_opts.keys(), user_api, admin_api) except exception.InvalidInput: - # FIXME(comstud): I refactored code in here to support + # NOTE(comstud): I refactored code in here to support # new search options, and the original code ignored # invalid options. So, I've left it this way for now. # The v1.1 implementation will return an error in this -- cgit From 5a2add5c6011ce94f4727037c193274d21351cb2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 18 Jul 2011 04:13:22 -0700 Subject: another typo --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8df4ce31d..8c1638e21 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -588,7 +588,7 @@ class ControllerV11(Controller): context = req.environ['nova.context'] try: - check_option_permissions(context, search_opt.keys(), + check_option_permissions(context, search_opts.keys(), user_api, admin_api) except exception.InvalidInput, e: raise faults.Fault(exc.HTTPBadRequest(explanation=str(e))) -- cgit From 0b9048bc3285b86a073da9aa9327815319aaa184 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 19 Jul 2011 12:44:00 -0700 Subject: allow 'marker' and 'limit' in search options. fix log format error --- nova/api/openstack/servers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8c1638e21..17a3df344 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -64,8 +64,10 @@ def check_option_permissions(context, specified_options, if opt not in known_options] if spec_unknown_opts: unknown_opt_str = ", ".join(spec_unknown_opts) + LOG.error(_("Received request for unknown options " + "'%(unknown_opt_str)s'") % locals()) raise exception.InvalidInput(reason=_( - "Unknown options specified: %(unknown_opt_str)")) + "Unknown options specified: %(unknown_opt_str)s")) # Check for admin context for the admin commands if not context.is_admin: @@ -73,6 +75,9 @@ def check_option_permissions(context, specified_options, if opt in admin_api_options] if spec_admin_opts: admin_opt_str = ", ".join(admin_opts) + LOG.error(_("Received request for admin options " + "'%(admin_opt_str)s' from non-admin context") % + locals()) raise exception.AdminRequired() @@ -471,9 +476,9 @@ class ControllerV10(Controller): search_opts = {} search_opts.update(req.str_GET) - user_api = ['project_id', 'fixed_ip', 'recurse_zones', - 'reservation_id', 'name', 'fresh', 'status', - 'image', 'flavor'] + user_api = ['marker', 'limit', 'project_id', 'fixed_ip', + 'recurse_zones', 'reservation_id', 'name', 'fresh', + 'status', 'image', 'flavor'] admin_api = ['ip', 'ip6', 'instance_name'] context = req.environ['nova.context'] @@ -581,8 +586,8 @@ class ControllerV11(Controller): search_opts = {} search_opts.update(req.str_GET) - user_api = ['image', 'flavor', 'name', 'status', - 'reservation_id', 'changes-since'] + user_api = ['marker', 'limit', 'image', 'flavor', 'name', + 'status', 'reservation_id', 'changes-since'] admin_api = ['ip', 'ip6', 'instance_name'] context = req.environ['nova.context'] -- cgit From 7630aa8acc376364375ef48a3d955a7c21f50b04 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 11:02:00 -0700 Subject: added API tests for search options fixed a couple of bugs the tests caught --- nova/api/openstack/servers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 17a3df344..ed3f82039 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -67,14 +67,15 @@ def check_option_permissions(context, specified_options, LOG.error(_("Received request for unknown options " "'%(unknown_opt_str)s'") % locals()) raise exception.InvalidInput(reason=_( - "Unknown options specified: %(unknown_opt_str)s")) + "Unknown options specified: %(unknown_opt_str)s") % + locals()) # Check for admin context for the admin commands if not context.is_admin: spec_admin_opts = [opt for opt in specified_options if opt in admin_api_options] if spec_admin_opts: - admin_opt_str = ", ".join(admin_opts) + admin_opt_str = ", ".join(spec_admin_opts) LOG.error(_("Received request for admin options " "'%(admin_opt_str)s' from non-admin context") % locals()) -- cgit From b1099b43f34e41676b0508267e9ad40b2c3415e3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 11:32:43 -0700 Subject: ec2 fixes --- nova/api/ec2/cloud.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 76725370a..9aef5079c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -832,9 +832,7 @@ class CloudController(object): for ec2_id in instance_id: internal_id = ec2utils.ec2_id_to_id(ec2_id) try: - instance = self.compute_api.get(context, - instance_id=internal_id, - search_opts=search_opts) + instance = self.compute_api.get(context, internal_id) except exception.NotFound: continue instances.append(instance) @@ -1021,7 +1019,7 @@ class CloudController(object): 'AvailabilityZone'), block_device_mapping=kwargs.get('block_device_mapping', {})) return self._format_run_instances(context, - instance_id=instances[0]['reservation_id']) + reservation_id=instances[0]['reservation_id']) def _do_instance(self, action, context, ec2_id): instance_id = ec2utils.ec2_id_to_id(ec2_id) -- cgit From a1cb17bf98359fae760800f8467c897d859b6994 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 12:16:23 -0700 Subject: minor fixups --- nova/api/openstack/servers.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ed3f82039..8ec74b387 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -64,11 +64,10 @@ def check_option_permissions(context, specified_options, if opt not in known_options] if spec_unknown_opts: unknown_opt_str = ", ".join(spec_unknown_opts) - LOG.error(_("Received request for unknown options " - "'%(unknown_opt_str)s'") % locals()) - raise exception.InvalidInput(reason=_( - "Unknown options specified: %(unknown_opt_str)s") % - locals()) + reason = _("Received request for unknown options " + "'%(unknown_opt_str)s'") % locals() + LOG.error(reason) + raise exception.InvalidInput(reason=reason) # Check for admin context for the admin commands if not context.is_admin: @@ -136,6 +135,11 @@ class Controller(object): reason = _('Invalid server status: %(status)s') % locals() LOG.error(reason) raise exception.InvalidInput(reason=reason) + + # Don't pass these along to compute API, if they exist. + search_opts.pop('changes-since', None) + search_opts.pop('fresh', None) + instance_list = self.compute_api.get_all( context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) -- cgit From 994e219ab0b25d48b31484a43a0ac12099cf226e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 00:46:38 -0700 Subject: rework OS API checking of search options --- nova/api/openstack/servers.py | 181 ++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 111 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2573fc48c..b028d3a40 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -41,44 +41,46 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -def check_option_permissions(context, specified_options, - user_api_options, admin_api_options): - """Check whether or not entries in 'specified_options' are valid - based on the allowed 'user_api_options' and 'admin_api_options'. +def check_admin_search_options(context, search_options, admin_api_options): + """Check for any 'admin_api_options' specified in 'search_options'. + + If admin api is not enabled, we should pretend that we know nothing + about those options.. Ie, they don't exist in user-facing API. To + achieve this, we will strip any admin options that we find from + search_options + + If admin api is enabled, we should require admin context for any + admin options specified, and return an exception in this case. + + If any exist and admin api is not enabled, strip them from + search_options (has the effect of treating them like they don't exist). + + search_options is a dictionary of "search_option": value + admin_api_options is a list + + Returns: None if options are okay. + Modifies: admin options could be stripped from search_options + Raises: exception.AdminRequired for needing admin context + """ - All inputs are lists of option names + if not FLAGS.allow_admin_api: + # Remove any admin_api_options from search_options + for option in admin_api_options: + search_options.pop(option, None) + return - Returns: exception.InvalidInput for an invalid option or - exception.AdminRequired for needing admin privs - """ + # allow_admin_api is True and admin context? Any command is okay. + if context.is_admin: + return - # We pretend we don't know about admin_api_options if the admin - # API is not enabled. - if FLAGS.allow_admin_api: - known_options = user_api_options + admin_api_options - else: - known_options = user_api_options - - # Check for unknown query string params. - spec_unknown_opts = [opt for opt in specified_options - if opt not in known_options] - if spec_unknown_opts: - unknown_opt_str = ", ".join(spec_unknown_opts) - reason = _("Received request for unknown options " - "'%(unknown_opt_str)s'") % locals() - LOG.error(reason) - raise exception.InvalidInput(reason=reason) - - # Check for admin context for the admin commands - if not context.is_admin: - spec_admin_opts = [opt for opt in specified_options - if opt in admin_api_options] - if spec_admin_opts: - admin_opt_str = ", ".join(spec_admin_opts) - LOG.error(_("Received request for admin options " - "'%(admin_opt_str)s' from non-admin context") % - locals()) - raise exception.AdminRequired() + spec_admin_opts = [opt for opt in search_options.iterkeys() + if opt in admin_api_options] + if spec_admin_opts: + admin_opt_str = ", ".join(spec_admin_opts) + LOG.error(_("Received request for admin-only search options " + "'%(admin_opt_str)s' from non-admin context") % + locals()) + raise exception.AdminRequired() class Controller(object): @@ -91,7 +93,7 @@ class Controller(object): def index(self, req): """ Returns a list of server names and ids for a given user """ try: - servers = self._items(req, is_detail=False) + servers = self._servers_from_request(req, is_detail=False) except exception.Invalid as err: return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) except exception.NotFound: @@ -101,7 +103,7 @@ class Controller(object): def detail(self, req): """ Returns a list of server details for a given user """ try: - servers = self._items(req, is_detail=True) + servers = self._servers_from_request(req, is_detail=True) except exception.Invalid as err: return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) except exception.NotFound as err: @@ -117,10 +119,9 @@ class Controller(object): def _action_rebuild(self, info, request, instance_id): raise NotImplementedError() - def _get_items(self, context, req, is_detail, search_opts=None): - """Returns a list of servers. - - builder - the response model builder + def _servers_search(self, context, req, is_detail, search_opts=None): + """Returns a list of servers, taking into account any search + options specified. """ if search_opts is None: @@ -147,6 +148,34 @@ class Controller(object): for inst in limited_list] return dict(servers=servers) + def _servers_from_request(self, req, is_detail): + """Returns a list of servers based on the request. + + Checks for search options and permissions on the options. + """ + + search_opts = {} + search_opts.update(req.str_GET) + + admin_api = ['ip', 'ip6', 'instance_name'] + + context = req.environ['nova.context'] + + try: + check_admin_search_options(context, search_opts, admin_api) + except exception.AdminRequired, e: + raise exc.HTTPForbidden(detail=str(e)) + + # Convert recurse_zones into a boolean + search_opts['recurse_zones'] = utils.bool_from_str( + search_opts.get('recurse_zones', False)) + # convert flavor into an int + if 'flavor' in search_opts: + search_opts['flavor'] = int(search_opts['flavor']) + + return self._servers_search(context, req, is_detail, + search_opts=search_opts) + @scheduler_api.redirect_handler def show(self, req, id): """ Returns server details by server id """ @@ -469,45 +498,6 @@ class ControllerV10(Controller): raise exc.HTTPNotFound() return webob.Response(status_int=202) - def _items(self, req, is_detail): - """Returns a list of servers based on the request. - - Checks for search options and permissions on the options. - """ - - search_opts = {} - search_opts.update(req.str_GET) - - user_api = ['marker', 'limit', 'project_id', 'fixed_ip', - 'recurse_zones', 'reservation_id', 'name', 'fresh', - 'status', 'image', 'flavor'] - admin_api = ['ip', 'ip6', 'instance_name'] - - context = req.environ['nova.context'] - - try: - check_option_permissions(context, search_opts.keys(), - user_api, admin_api) - except exception.InvalidInput: - # NOTE(comstud): I refactored code in here to support - # new search options, and the original code ignored - # invalid options. So, I've left it this way for now. - # The v1.1 implementation will return an error in this - # case.. - pass - except exception.AdminRequired, e: - raise faults.Fault(exc.HTTPForbidden(detail=str(e))) - - # Convert recurse_zones into a boolean - search_opts['recurse_zones'] = utils.bool_from_str( - search_opts.get('recurse_zones', False)) - # convert flavor into an int - if 'flavor' in search_opts: - search_opts['flavor'] = int(search_opts['flavor']) - - return self._get_items(context, req, is_detail, - search_opts=search_opts) - def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -572,37 +562,6 @@ class ControllerV11(Controller): except exception.NotFound: raise exc.HTTPNotFound() - def _items(self, req, is_detail): - """Returns a list of servers based on the request. - - Checks for search options and permissions on the options. - """ - - search_opts = {} - search_opts.update(req.str_GET) - - user_api = ['marker', 'limit', 'image', 'flavor', 'name', - 'status', 'reservation_id', 'changes-since'] - admin_api = ['ip', 'ip6', 'instance_name'] - - context = req.environ['nova.context'] - - try: - check_option_permissions(context, search_opts.keys(), - user_api, admin_api) - except exception.InvalidInput, e: - raise faults.Fault(exc.HTTPBadRequest(explanation=str(e))) - except exception.AdminRequired, e: - raise faults.Fault(exc.HTTPForbidden(explanation=str(e))) - - # NOTE(comstud): Making recurse_zones always be True in v1.1 - search_opts['recurse_zones'] = True - # convert flavor into an int - if 'flavor' in search_opts: - search_opts['flavor'] = int(search_opts['flavor']) - return self._get_items(context, req, is_detail, - search_opts=search_opts) - def _image_ref_from_req_data(self, data): return data['server']['imageRef'] -- cgit From a5390a5b1cb95ca9aee6e2f99572498dd60b48e5 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 00:53:13 -0700 Subject: remove faults.Fault wrapper on exceptions --- nova/api/openstack/servers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b028d3a40..6ad549c97 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -95,9 +95,9 @@ class Controller(object): try: servers = self._servers_from_request(req, is_detail=False) except exception.Invalid as err: - return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) + return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPNotFound() return servers def detail(self, req): @@ -105,9 +105,9 @@ class Controller(object): try: servers = self._servers_from_request(req, is_detail=True) except exception.Invalid as err: - return faults.Fault(exc.HTTPBadRequest(explanation=str(err))) + return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound as err: - return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPNotFound() return servers def _build_view(self, req, instance, is_detail=False): -- cgit From c0851f2ec5be12c43cc96367e22220d25589e4ae Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 01:36:12 -0700 Subject: cleanup checking of options in the API before calling compute_api's get_all() --- nova/api/openstack/servers.py | 104 ++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 59 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 965cf0bfc..e3e829e81 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -44,55 +44,25 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -def check_admin_search_options(context, search_options, admin_api_options): - """Check for any 'admin_api_options' specified in 'search_options'. - - If admin api is not enabled, we should pretend that we know nothing - about those options.. Ie, they don't exist in user-facing API. To - achieve this, we will strip any admin options that we find from - search_options - - If admin api is enabled, we should require admin context for any - admin options specified, and return an exception in this case. - - If any exist and admin api is not enabled, strip them from - search_options (has the effect of treating them like they don't exist). - - search_options is a dictionary of "search_option": value - admin_api_options is a list - - Returns: None if options are okay. - Modifies: admin options could be stripped from search_options - Raises: exception.AdminRequired for needing admin context - """ - - if not FLAGS.allow_admin_api: - # Remove any admin_api_options from search_options - for option in admin_api_options: - search_options.pop(option, None) - return - - # allow_admin_api is True and admin context? Any command is okay. - if context.is_admin: - return - - spec_admin_opts = [opt for opt in search_options.iterkeys() - if opt in admin_api_options] - if spec_admin_opts: - admin_opt_str = ", ".join(spec_admin_opts) - LOG.error(_("Received request for admin-only search options " - "'%(admin_opt_str)s' from non-admin context") % - locals()) - raise exception.AdminRequired() - - class Controller(object): - """ The Server API controller for the OpenStack API """ + """ The Server API base controller class for the OpenStack API """ + + servers_search_options = [] def __init__(self): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) + def _check_servers_options(self, search_options): + if FLAGS.allow_admin_api and context.is_admin: + # Allow all options + return + # Otherwise, strip out all unknown options + unknown_options = [opt for opt in search_options + if opt not in self.servers_search_options] + for opt in unknown_options: + search_options.pop(opt, None) + def index(self, req): """ Returns a list of server names and ids for a given user """ try: @@ -131,21 +101,38 @@ class Controller(object): search_opts = {} # If search by 'status', we need to convert it to 'state' - # If the status is unknown, bail - status = search_opts.pop('status', None) - if status is not None: + # If the status is unknown, bail. + # Leave 'state' in search_opts so compute can pass it on to + # child zones.. + if 'status' in search_opts: + status = search_opts['status'] search_opts['state'] = power_state.states_from_status(status) if len(search_opts['state']) == 0: reason = _('Invalid server status: %(status)s') % locals() LOG.error(reason) raise exception.InvalidInput(reason=reason) - # Don't pass these along to compute API, if they exist. - search_opts.pop('changes-since', None) - search_opts.pop('fresh', None) + # 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. + # ... Unless 'changes-since' is specified, because 'changes-since' + # 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 + 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? + limited_list = self._limit_items(instance_list, req) servers = [self._build_view(req, inst, is_detail)['server'] for inst in limited_list] @@ -160,21 +147,12 @@ class Controller(object): search_opts = {} search_opts.update(req.str_GET) - admin_api = ['ip', 'ip6', 'instance_name'] - context = req.environ['nova.context'] - - try: - check_admin_search_options(context, search_opts, admin_api) - except exception.AdminRequired, e: - raise exc.HTTPForbidden(detail=str(e)) + self._check_servers_options(context, search_opts) # Convert recurse_zones into a boolean search_opts['recurse_zones'] = utils.bool_from_str( search_opts.get('recurse_zones', False)) - # convert flavor into an int - if 'flavor' in search_opts: - search_opts['flavor'] = int(search_opts['flavor']) return self._servers_search(context, req, is_detail, search_opts=search_opts) @@ -581,6 +559,10 @@ class Controller(object): class ControllerV10(Controller): + """v1.0 OpenStack API controller""" + + servers_search_options = ["reservation_id", "fixed_ip", + "name", "recurse_zones"] @scheduler_api.redirect_handler def delete(self, req, id): @@ -645,6 +627,10 @@ class ControllerV10(Controller): class ControllerV11(Controller): + """v1.1 OpenStack API controller""" + + servers_search_options = ["reservation_id", "name", "recurse_zones", + "status", "image", "flavor", "changes-since"] @scheduler_api.redirect_handler def delete(self, req, id): -- cgit From b1b919d42d8c359fc9ae981b44466d269fc688a6 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 13:59:57 -0700 Subject: test fixes and typos --- nova/api/openstack/common.py | 33 +++++++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 9 ++++----- nova/api/openstack/views/servers.py | 3 +-- 3 files changed, 38 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..bbf46261b 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -25,6 +25,7 @@ from nova import exception from nova import flags from nova import log as logging from nova.api.openstack import wsgi +from nova.compute import power_state as compute_power_state LOG = logging.getLogger('nova.api.openstack.common') @@ -35,6 +36,38 @@ 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', +} + + +def status_from_power_state(power_state): + """Map the power state to the server status string""" + return _STATUS_MAP[power_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 get_pagination_params(request): """Return marker, limit tuple from request. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e3e829e81..c842fcc01 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -31,7 +31,6 @@ from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import ips from nova.api.openstack import wsgi from nova.compute import instance_types -from nova.compute import power_state from nova.scheduler import api as scheduler_api import nova.api.openstack import nova.api.openstack.views.addresses @@ -53,7 +52,7 @@ class Controller(object): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) - def _check_servers_options(self, search_options): + def _check_servers_options(self, context, search_options): if FLAGS.allow_admin_api and context.is_admin: # Allow all options return @@ -106,7 +105,7 @@ class Controller(object): # child zones.. if 'status' in search_opts: status = search_opts['status'] - search_opts['state'] = power_state.states_from_status(status) + search_opts['state'] = common.power_states_from_status(status) if len(search_opts['state']) == 0: reason = _('Invalid server status: %(status)s') % locals() LOG.error(reason) @@ -122,10 +121,10 @@ class Controller(object): # Admin hasn't specified deleted filter if 'changes-since' not in search_opts: # No 'changes-since', so we need to find non-deleted servers - search_opts['deleted'] = '^False$' + search_opts['deleted'] = False else: # This is the default, but just in case.. - search_opts['deleted'] = '^True$' + search_opts['deleted'] = True instance_list = self.compute_api.get_all( context, search_opts=search_opts) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 64be00f86..8222f6766 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -20,7 +20,6 @@ import hashlib import os from nova import exception -from nova.compute import power_state import nova.compute import nova.context from nova.api.openstack import common @@ -65,7 +64,7 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'status': power_state.status_from_state(inst.get('state'))} + 'status': common.status_from_power_state(inst.get('state'))} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() -- cgit From e36232aed703eca43c6eb6df02a5c2aa0a1ac649 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 14:40:06 -0700 Subject: fix OS API tests --- nova/api/openstack/servers.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c842fcc01..e10e5bc86 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -59,9 +59,14 @@ class Controller(object): # Otherwise, strip out all unknown options unknown_options = [opt for opt in search_options if opt not in self.servers_search_options] + unk_opt_str = ", ".join(unknown_options) + log_msg = _("Stripping out options '%(unk_opt_str)s' from servers " + "query") % locals() + LOG.debug(log_msg) for opt in unknown_options: search_options.pop(opt, None) + def index(self, req): """ Returns a list of server names and ids for a given user """ try: -- cgit From 2b45204e593f9330c8b961cfae3ad5af0bd36642 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 14:47:05 -0700 Subject: doc string fix --- nova/api/openstack/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e10e5bc86..b89b24047 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -145,7 +145,8 @@ class Controller(object): def _servers_from_request(self, req, is_detail): """Returns a list of servers based on the request. - Checks for search options and permissions on the options. + Checks for search options and strips out options that should + not be available to non-admins. """ search_opts = {} -- cgit From 64966cbd83cbde6a240dad4ac786fe7a6a116f2f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 17:30:07 -0700 Subject: pep8 fixes --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 63791bcd1..b54897b8a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -66,7 +66,6 @@ class Controller(object): for opt in unknown_options: search_options.pop(opt, None) - def index(self, req): """ Returns a list of server names and ids for a given user """ try: -- cgit From 8aeec07c2a5f8a5f1cfb049e20caa29295496606 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 17:36:39 -0700 Subject: add comment for servers_search_options list in the OS API Controllers. --- nova/api/openstack/servers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b54897b8a..ae0b103bd 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -46,6 +46,10 @@ FLAGS = flags.FLAGS class Controller(object): """ The Server API base controller class for the OpenStack API """ + # These are a list of possible query string paramters to the + # /servers query that a user should be able to do. Specify this + # in your subclasses. When admin api is off, unknown options will + # get filtered out without error. servers_search_options = [] def __init__(self): @@ -565,6 +569,9 @@ class Controller(object): class ControllerV10(Controller): """v1.0 OpenStack API controller""" + # These are a list of possible query string paramters to the + # /servers query that a user should be able to do. When admin api + # is off, unknown options will get filtered out without error. servers_search_options = ["reservation_id", "fixed_ip", "name", "recurse_zones"] @@ -633,6 +640,9 @@ class ControllerV10(Controller): class ControllerV11(Controller): """v1.1 OpenStack API controller""" + # These are a list of possible query string paramters to the + # /servers query that a user should be able to do. When admin api + # is off, unknown options will get filtered out without error. servers_search_options = ["reservation_id", "name", "recurse_zones", "status", "image", "flavor", "changes-since"] -- cgit From fd9a761f25c6095d1ea47e09cdac503683b44bfc Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 17:59:22 -0700 Subject: pep8 fix --- nova/api/openstack/servers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ae0b103bd..b3776ed44 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -46,7 +46,7 @@ FLAGS = flags.FLAGS class Controller(object): """ The Server API base controller class for the OpenStack API """ - # These are a list of possible query string paramters to the + # These are a list of possible query string paramters to the # /servers query that a user should be able to do. Specify this # in your subclasses. When admin api is off, unknown options will # get filtered out without error. @@ -569,7 +569,7 @@ class Controller(object): class ControllerV10(Controller): """v1.0 OpenStack API controller""" - # These are a list of possible query string paramters to the + # These are a list of possible query string paramters to the # /servers query that a user should be able to do. When admin api # is off, unknown options will get filtered out without error. servers_search_options = ["reservation_id", "fixed_ip", @@ -640,7 +640,7 @@ class ControllerV10(Controller): class ControllerV11(Controller): """v1.1 OpenStack API controller""" - # These are a list of possible query string paramters to the + # These are a list of possible query string paramters to the # /servers query that a user should be able to do. When admin api # is off, unknown options will get filtered out without error. servers_search_options = ["reservation_id", "name", "recurse_zones", -- cgit From b2fe710c59ba266b9afd67db1cae60a6db5c71e3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 5 Aug 2011 15:06:07 -0700 Subject: rename _check_servers_options, add some comments and small cleanup in the db get_by_filters call --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b3776ed44..9a3872113 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -56,7 +56,7 @@ class Controller(object): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) - def _check_servers_options(self, context, search_options): + def _remove_invalid_options(self, context, search_options): if FLAGS.allow_admin_api and context.is_admin: # Allow all options return @@ -156,7 +156,7 @@ class Controller(object): search_opts.update(req.str_GET) context = req.environ['nova.context'] - self._check_servers_options(context, search_opts) + self._remove_invalid_options(context, search_opts) # Convert recurse_zones into a boolean search_opts['recurse_zones'] = utils.bool_from_str( -- cgit From ca152762aa73a93583be2ada237cf8bbbcc99220 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 6 Jul 2011 05:41:47 -0700 Subject: clean up OS API servers getting --- nova/api/openstack/servers.py | 94 +++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 57 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9a3872113..ce04a1eab 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -46,34 +46,14 @@ FLAGS = flags.FLAGS class Controller(object): """ The Server API base controller class for the OpenStack API """ - # These are a list of possible query string paramters to the - # /servers query that a user should be able to do. Specify this - # in your subclasses. When admin api is off, unknown options will - # get filtered out without error. - servers_search_options = [] - def __init__(self): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) - def _remove_invalid_options(self, context, search_options): - if FLAGS.allow_admin_api and context.is_admin: - # Allow all options - return - # Otherwise, strip out all unknown options - unknown_options = [opt for opt in search_options - if opt not in self.servers_search_options] - unk_opt_str = ", ".join(unknown_options) - log_msg = _("Stripping out options '%(unk_opt_str)s' from servers " - "query") % locals() - LOG.debug(log_msg) - for opt in unknown_options: - search_options.pop(opt, None) - def index(self, req): """ Returns a list of server names and ids for a given user """ try: - servers = self._servers_from_request(req, is_detail=False) + servers = self._get_servers(req, is_detail=False) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: @@ -83,7 +63,7 @@ class Controller(object): def detail(self, req): """ Returns a list of server details for a given user """ try: - servers = self._servers_from_request(req, is_detail=True) + servers = self._get_servers(req, is_detail=True) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound as err: @@ -99,13 +79,21 @@ class Controller(object): def _action_rebuild(self, info, request, instance_id): raise NotImplementedError() - def _servers_search(self, context, req, is_detail, search_opts=None): + def _get_servers(self, req, is_detail): """Returns a list of servers, taking into account any search options specified. """ - if search_opts is None: - search_opts = {} + search_opts = {} + search_opts.update(req.str_GET) + + context = req.environ['nova.context'] + remove_invalid_options(context, search_opts, + self._get_server_search_options()) + + # Convert recurse_zones into a boolean + 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. @@ -145,26 +133,6 @@ class Controller(object): for inst in limited_list] return dict(servers=servers) - def _servers_from_request(self, req, is_detail): - """Returns a list of servers based on the request. - - Checks for search options and strips out options that should - not be available to non-admins. - """ - - search_opts = {} - search_opts.update(req.str_GET) - - context = req.environ['nova.context'] - self._remove_invalid_options(context, search_opts) - - # Convert recurse_zones into a boolean - search_opts['recurse_zones'] = utils.bool_from_str( - search_opts.get('recurse_zones', False)) - - return self._servers_search(context, req, is_detail, - search_opts=search_opts) - @scheduler_api.redirect_handler def show(self, req, id): """ Returns server details by server id """ @@ -569,12 +537,6 @@ class Controller(object): class ControllerV10(Controller): """v1.0 OpenStack API controller""" - # These are a list of possible query string paramters to the - # /servers query that a user should be able to do. When admin api - # is off, unknown options will get filtered out without error. - servers_search_options = ["reservation_id", "fixed_ip", - "name", "recurse_zones"] - @scheduler_api.redirect_handler def delete(self, req, id): """ Destroys a server """ @@ -636,16 +598,14 @@ class ControllerV10(Controller): """ Determine the admin password for a server on creation """ return self.helper._get_server_admin_password_old_style(server) + def _get_server_search_options(self): + """Return server search options allowed by non-admin""" + return 'reservation_id', 'fixed_ip', 'name', 'recurse_zones' + class ControllerV11(Controller): """v1.1 OpenStack API controller""" - # These are a list of possible query string paramters to the - # /servers query that a user should be able to do. When admin api - # is off, unknown options will get filtered out without error. - servers_search_options = ["reservation_id", "name", "recurse_zones", - "status", "image", "flavor", "changes-since"] - @scheduler_api.redirect_handler def delete(self, req, id): """ Destroys a server """ @@ -812,6 +772,11 @@ class ControllerV11(Controller): """ Determine the admin password for a server on creation """ return self.helper._get_server_admin_password_new_style(server) + def _get_server_search_options(self): + """Return server search options allowed by non-admin""" + return ('reservation_id', 'name', 'recurse_zones', + 'status', 'image', 'flavor', 'changes-since') + class HeadersSerializer(wsgi.ResponseHeadersSerializer): @@ -982,3 +947,18 @@ def create_resource(version='1.0'): deserializer = wsgi.RequestDeserializer(body_deserializers) return wsgi.Resource(controller, deserializer, serializer) + + +def remove_invalid_options(context, search_options, allowed_search_options): + """Remove search options that are not valid for non-admin API/context""" + if FLAGS.allow_admin_api and context.is_admin: + # Allow all options + return + # Otherwise, strip out all unknown options + unknown_options = [opt for opt in search_options + if opt not in allowed_search_options] + unk_opt_str = ", ".join(unknown_options) + log_msg = _("Removing options '%(unk_opt_str)s' from query") % locals() + LOG.debug(log_msg) + for opt in unknown_options: + search_options.pop(opt, None) -- cgit From 1c90eb34085dbb69f37e2f63dea7496afabb06b3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 6 Jul 2011 06:20:38 -0700 Subject: clean up compute_api.get_all filter name remappings. ditch fixed_ip one-off code. fixed ec2 api call to this to compensate --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 966c3a564..db49baffa 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -242,7 +242,7 @@ class CloudController(object): search_opts=search_opts) except exception.NotFound: instance_ref = None - if instance_ref is None: + if not instance_ref: return None # This ensures that all attributes of the instance -- cgit From 6b83e1cd31f5e138af20fbd5c118d55da092eb35 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 7 Jul 2011 15:24:12 +0000 Subject: Added API and supporting code for rebooting or shutting down XenServer hosts. --- nova/api/openstack/contrib/hosts.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 55e57e1a4..cc71cadbd 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,6 +78,12 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) + elif key == "power_state": + if val in ("reboot", "off", "on"): + return self._set_power_state(req, id, val) + else: + explanation = _("Invalid status: '%s'") % raw_val + raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -89,8 +95,28 @@ class HostController(object): LOG.audit(_("Setting host %(host)s to %(state)s.") % locals()) result = self.compute_api.set_host_enabled(context, host=host, enabled=enabled) + if result not in ("enabled", "disabled"): + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result} + def _set_power_state(self, req, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + context = req.environ['nova.context'] + if power_state == "on": + raise webob.exc.HTTPNotImplemented() + if power_state == "reboot": + msg = _("Rebooting host %(host)s") + else: + msg = _("Powering off host %(host)s.") + LOG.audit(msg % locals()) + result = self.compute_api.set_power_state(context, host=host, + power_state=power_state) + if result != power_state: + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) + return {"host": host, "power_state": result} + class Hosts(extensions.ExtensionDescriptor): def get_name(self): -- cgit From 5c6e4aa80672966ad4449007feea970cd62dee10 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sun, 17 Jul 2011 23:52:50 +0100 Subject: Some basic validation for creating ec2 security groups. (LP: #715443) --- nova/api/ec2/__init__.py | 4 ++++ nova/api/ec2/cloud.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 890d57fe7..027e35933 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -349,6 +349,10 @@ class Executor(wsgi.Application): LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) + except exception.InvalidParameterValue as ex: + LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex), + context=context) + return self._error(req, context, type(ex).__name__, unicode(ex)) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index acfd1361c..3ef64afa7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -28,6 +28,7 @@ import os import urllib import tempfile import shutil +import re from nova import compute from nova import context @@ -602,6 +603,22 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): + if not re.match('^[a-zA-Z0-9_\- ]+$',group_name): + # Some validation to ensure that values match API spec. + # - Alphanumeric characters, spaces, dashes, and underscores. + # TODO(Daviey): extend beyond group_name checking, and probably + # create a param validator function that can be used elsewhere. + err = _("Value (%s) for parameter GroupName is invalid." + " Content limited to Alphanumeric characters, " + "spaces, dashes, and underscores.") % group_name + # err not that of master ec2 implementation, as they fail to raise. + raise exception.InvalidParameterValue(err=err) + + if len(str(group_name)) > 255: + err = _("Value (%s) for parameter GroupName is invalid." + " Length exceeds maximum of 255.") % group_name + raise exception.InvalidParameterValue(err=err) + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): -- cgit From 9d0b441939ab5a9227e91bb868f499d700c7c907 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:16:53 +0100 Subject: pep8'd --- nova/api/ec2/cloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 3ef64afa7..8d7aa9953 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -603,11 +603,11 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): - if not re.match('^[a-zA-Z0-9_\- ]+$',group_name): + if not re.match('^[a-zA-Z0-9_\- ]+$', group_name): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): extend beyond group_name checking, and probably - # create a param validator function that can be used elsewhere. + # TODO(Daviey): extend beyond group_name checking, and probably + # create a param validator function that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " "spaces, dashes, and underscores.") % group_name -- cgit From beb2337f002178b7e764f3a6dcbab4637321aa34 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:22:01 +0100 Subject: nova/api/ec2/cloud.py: Rearranged imports to be alphabetical as per HACKING. --- nova/api/ec2/cloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8d7aa9953..9de5b58f5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -25,10 +25,10 @@ datastore. import base64 import netaddr import os -import urllib -import tempfile -import shutil import re +import shutil +import tempfile +import urllib from nova import compute from nova import context -- cgit From e68d53df98890f424e361c7c79a5b2cd62723963 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:41:51 +0100 Subject: convert group_name to string, incase it's a long --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9de5b58f5..e4b008b85 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -603,7 +603,7 @@ class CloudController(object): return source_project_id def create_security_group(self, context, group_name, group_description): - if not re.match('^[a-zA-Z0-9_\- ]+$', group_name): + if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. # TODO(Daviey): extend beyond group_name checking, and probably -- cgit From 25bd75bfd2c72899bf139e671fd42fd2dc1dc0e1 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Wed, 20 Jul 2011 20:12:19 +0100 Subject: Added LP bug num to TODO --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cd493e3e7..ecdbad895 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -695,8 +695,8 @@ class CloudController(object): if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): extend beyond group_name checking, and probably - # create a param validator function that can be used elsewhere. + # TODO(Daviey): LP: #813685 extend beyond group_name checking, and + # probably create a param validator that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " "spaces, dashes, and underscores.") % group_name -- cgit From 1f55e116adbf00a0a5bd990f99a680e9d6b1618e Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: ec2utils: factor generic helper function into generic place This patch moves out a helper function, properties_root_device_name(), into generic file nova/block_device.py. --- nova/api/ec2/cloud.py | 5 +++-- nova/api/ec2/ec2utils.py | 19 ------------------- 2 files changed, 3 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 16ca1ed2a..b25f74f61 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -30,6 +30,7 @@ import tempfile import time import shutil +from nova import block_device from nova import compute from nova import context @@ -1240,7 +1241,7 @@ class CloudController(object): i['architecture'] = image['properties'].get('architecture') properties = image['properties'] - root_device_name = ec2utils.properties_root_device_name(properties) + root_device_name = block_device.properties_root_device_name(properties) root_device_type = 'instance-store' for bdm in properties.get('block_device_mapping', []): if (bdm.get('device_name') == root_device_name and @@ -1313,7 +1314,7 @@ class CloudController(object): def _root_device_name_attribute(image, result): result['rootDeviceName'] = \ - ec2utils.properties_root_device_name(image['properties']) + block_device.properties_root_device_name(image['properties']) if result['rootDeviceName'] is None: result['rootDeviceName'] = _DEFAULT_ROOT_DEVICE_NAME diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index bae1e0ee5..14891debb 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -137,25 +137,6 @@ def dict_from_dotted_str(items): return args -def properties_root_device_name(properties): - """get root device name from image meta data. - If it isn't specified, return None. - """ - root_device_name = None - - # NOTE(yamahata): see image_service.s3.s3create() - for bdm in properties.get('mappings', []): - if bdm['virtual'] == 'root': - root_device_name = bdm['device'] - - # NOTE(yamahata): register_image's command line can override - # .manifest.xml - if 'root_device_name' in properties: - root_device_name = properties['root_device_name'] - - return root_device_name - - def mappings_prepend_dev(mappings): """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" for m in mappings: -- cgit From ba6b6a20eeedb0311e06090d2f60d36964d67cf4 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: block_device: introduce helper function to check swap or ephemeral device and move generic function, mappings_prepend_dev() from ec2utils to block_device --- nova/api/ec2/cloud.py | 8 +++----- nova/api/ec2/ec2utils.py | 10 ---------- 2 files changed, 3 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b25f74f61..c35194f6f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -106,7 +106,7 @@ def _parse_block_device_mapping(bdm): def _properties_get_mappings(properties): - return ec2utils.mappings_prepend_dev(properties.get('mappings', [])) + return block_device.mappings_prepend_dev(properties.get('mappings', [])) def _format_block_device_mapping(bdm): @@ -145,8 +145,7 @@ def _format_mappings(properties, result): """Format multiple BlockDeviceMappingItemType""" mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']} for m in _properties_get_mappings(properties) - if (m['virtual'] == 'swap' or - m['virtual'].startswith('ephemeral'))] + if block_device.is_swap_or_ephemeral(m['virtual'])] block_device_mapping = [_format_block_device_mapping(bdm) for bdm in properties.get('block_device_mapping', [])] @@ -1447,8 +1446,7 @@ class CloudController(object): if virtual_name in ('ami', 'root'): continue - assert (virtual_name == 'swap' or - virtual_name.startswith('ephemeral')) + assert block_device.is_swap_or_ephemeral(virtual_name) device_name = m['device'] if device_name in [b['device_name'] for b in mapping if not b.get('no_device', False)]: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 14891debb..bcdf2ba78 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -135,13 +135,3 @@ def dict_from_dotted_str(items): args[key] = value return args - - -def mappings_prepend_dev(mappings): - """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" - for m in mappings: - virtual = m['virtual'] - if ((virtual == 'swap' or virtual.startswith('ephemeral')) and - (not m['device'].startswith('/'))): - m['device'] = '/dev/' + m['device'] - return mappings -- cgit From 92ac32e148d31a957be6e8f3e90724216e10106a Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: api/ec2: implement describe_instance_attribute() This patch implements DescribeInstanceAttribute. --- nova/api/ec2/cloud.py | 131 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c35194f6f..de75b912b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -260,7 +260,7 @@ class CloudController(object): instance_ref['id']) security_groups = [x['name'] for x in security_groups] data = { - 'user-data': base64.b64decode(instance_ref['user_data']), + 'user-data': self._format_user_data(instance_ref), 'meta-data': { 'ami-id': image_ec2_id, 'ami-launch-index': instance_ref['launch_index'], @@ -874,13 +874,102 @@ class CloudController(object): 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} - def _convert_to_set(self, lst, label): + @staticmethod + def _convert_to_set(lst, label): if lst is None or lst == []: return None if not isinstance(lst, list): lst = [lst] return [{label: x} for x in lst] + def _format_kernel_id(self, instance_ref, result, key): + kernel_id = instance_ref['kernel_id'] + if kernel_id is None: + return + result[key] = self.image_ec2_id(instance_ref['kernel_id'], 'aki') + + def _format_ramdisk_id(self, instance_ref, result, key): + ramdisk_id = instance_ref['ramdisk_id'] + if ramdisk_id is None: + return + result[key] = self.image_ec2_id(instance_ref['ramdisk_id'], 'ari') + + @staticmethod + def _format_user_data(instance_ref): + return base64.b64decode(instance_ref['user_data']) + + def describe_instance_attribute(self, context, instance_id, attribute, + **kwargs): + def _unsupported_attribute(instance, result): + raise exception.ApiError(_('attribute not supported: %s') % + attribute) + + def _format_attr_block_device_mapping(instance, result): + tmp = {} + self._format_instance_root_device_name(instance, tmp) + self._format_instance_bdm(context, instance_id, + tmp['rootDeviceName'], result) + + def _format_attr_disable_api_termination(instance, result): + _unsupported_attribute(instance, result) + + def _format_attr_group_set(instance, result): + CloudController._format_group_set(instance, result) + + 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) + if value: + result['instanceInitiatedShutdownBehavior'] = value + + def _format_attr_instance_type(instance, result): + self._format_instance_type(instance, result) + + def _format_attr_kernel(instance, result): + self._format_kernel_id(instance, result, 'kernel') + + def _format_attr_ramdisk(instance, result): + self._format_ramdisk_id(instance, result, 'ramdisk') + + def _format_attr_root_device_name(instance, result): + self._format_instance_root_device_name(instance, result) + + def _format_attr_source_dest_check(instance, result): + _unsupported_attribute(instance, result) + + def _format_attr_user_data(instance, result): + result['userData'] = self._format_user_data(instance) + + attribute_formatter = { + 'blockDeviceMapping': _format_attr_block_device_mapping, + 'disableApiTermination': _format_attr_disable_api_termination, + 'groupSet': _format_attr_group_set, + 'instanceInitiatedShutdownBehavior': + _format_attr_instance_initiated_shutdown_behavior, + 'instanceType': _format_attr_instance_type, + 'kernel': _format_attr_kernel, + 'ramdisk': _format_attr_ramdisk, + 'rootDeviceName': _format_attr_root_device_name, + 'sourceDestCheck': _format_attr_source_dest_check, + 'userData': _format_attr_user_data, + } + + fn = attribute_formatter.get(attribute) + if fn is None: + raise exception.ApiError( + _('attribute not supported: %s') % attribute) + + ec2_instance_id = instance_id + instance_id = ec2utils.ec2_id_to_id(ec2_instance_id) + instance = self.compute_api.get(context, instance_id) + result = {'instance_id': ec2_instance_id} + fn(instance, result) + return result + def describe_instances(self, context, **kwargs): return self._format_describe_instances(context, **kwargs) @@ -927,6 +1016,27 @@ class CloudController(object): result['blockDeviceMapping'] = mapping result['rootDeviceType'] = root_device_type + @staticmethod + def _format_instance_root_device_name(instance, result): + result['rootDeviceName'] = (instance.get('root_device_name') or + _DEFAULT_ROOT_DEVICE_NAME) + + @staticmethod + def _format_instance_type(instance, result): + if instance['instance_type']: + result['instanceType'] = instance['instance_type'].get('name') + else: + result['instanceType'] = None + + @staticmethod + def _format_group_set(instance, result): + security_group_names = [] + if instance.get('security_groups'): + for security_group in instance['security_groups']: + security_group_names.append(security_group['name']) + result['groupSet'] = CloudController._convert_to_set( + security_group_names, 'groupId') + def _format_instances(self, context, instance_id=None, **kwargs): # TODO(termie): this method is poorly named as its name does not imply # that it will be making a variety of database calls @@ -952,6 +1062,8 @@ class CloudController(object): ec2_id = ec2utils.id_to_ec2_id(instance_id) i['instanceId'] = ec2_id i['imageId'] = self.image_ec2_id(instance['image_ref']) + self._format_kernel_id(instance, i, 'kernelId') + self._format_ramdisk_id(instance, i, 'ramdiskId') i['instanceState'] = { 'code': instance['state'], 'name': instance['state_description']} @@ -980,16 +1092,12 @@ class CloudController(object): instance['project_id'], instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') - if instance['instance_type']: - i['instanceType'] = instance['instance_type'].get('name') - else: - i['instanceType'] = None + self._format_instance_type(instance, i) i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] - i['rootDeviceName'] = (instance.get('root_device_name') or - _DEFAULT_ROOT_DEVICE_NAME) + self._format_instance_root_device_name(instance, i) self._format_instance_bdm(context, instance_id, i['rootDeviceName'], i) host = instance['host'] @@ -999,12 +1107,7 @@ class CloudController(object): r = {} r['reservationId'] = instance['reservation_id'] r['ownerId'] = instance['project_id'] - security_group_names = [] - if instance.get('security_groups'): - for security_group in instance['security_groups']: - security_group_names.append(security_group['name']) - r['groupSet'] = self._convert_to_set(security_group_names, - 'groupId') + self._format_group_set(instance, r) r['instancesSet'] = [] reservations[instance['reservation_id']] = r reservations[instance['reservation_id']]['instancesSet'].append(i) -- cgit From a840e368235938a2fda96ab1694196e551ad22cc Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: ec2/get_metadata: teach block device mapping to get_metadata() This patch teachs bout block device mapping to get_metadata() --- nova/api/ec2/cloud.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index de75b912b..65f18ddbf 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -79,6 +79,10 @@ def _gen_key(context, user_id, key_name): # 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'} def _parse_block_device_mapping(bdm): @@ -233,6 +237,30 @@ class CloudController(object): state = 'available' return image['properties'].get('image_state', state) + def _get_instance_mapping(self, ctxt, instance_ref): + root_device_name = instance_ref['root_device_name'] + if root_device_name is None: + return _DEFAULT_MAPPINGS + + mappings = {} + mappings['ami'] = block_device.strip_dev(root_device_name) + mappings['root'] = root_device_name + + # 'ephemeralN' and 'swap' + for bdm in db.block_device_mapping_get_all_by_instance( + ctxt, instance_ref['id']): + if (bdm['volume_id'] or bdm['snapshot_id'] or bdm['no_device']): + continue + + virtual_name = bdm['virtual_name'] + if not virtual_name: + continue + + if block_device.is_swap_or_ephemeral(virtual_name): + mappings[virtual_name] = bdm['device_name'] + + return mappings + def get_metadata(self, address): ctxt = context.get_admin_context() instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) @@ -259,18 +287,14 @@ class CloudController(object): security_groups = db.security_group_get_by_instance(ctxt, instance_ref['id']) security_groups = [x['name'] for x in security_groups] + mappings = self._get_instance_mapping(ctxt, instance_ref) data = { 'user-data': self._format_user_data(instance_ref), 'meta-data': { 'ami-id': image_ec2_id, 'ami-launch-index': instance_ref['launch_index'], 'ami-manifest-path': 'FIXME', - 'block-device-mapping': { - # TODO(vish): replace with real data - 'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': _DEFAULT_ROOT_DEVICE_NAME, - 'swap': 'sda3'}, + 'block-device-mapping': mappings, 'hostname': hostname, 'instance-action': 'none', 'instance-id': ec2_id, -- cgit From 2e3b199005d16ee3e35cd6c71b8512628e3631bc Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Thu, 28 Jul 2011 21:12:03 +0100 Subject: Simplified test cases --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ecdbad895..371837d19 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -695,7 +695,7 @@ class CloudController(object): if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)): # Some validation to ensure that values match API spec. # - Alphanumeric characters, spaces, dashes, and underscores. - # TODO(Daviey): LP: #813685 extend beyond group_name checking, and + # TODO(Daviey): LP: #813685 extend beyond group_name checking, and # probably create a param validator that can be used elsewhere. err = _("Value (%s) for parameter GroupName is invalid." " Content limited to Alphanumeric characters, " -- cgit From a52b643b18e1bac18b642ecfd781809eb5612763 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 29 Jul 2011 10:51:50 +0900 Subject: api/ec2: rename CloudController._get_instance_mapping into _format_instance_mapping This patch renames nova.api.ec2.cloud.CouldController._get_instance_mapping to _format_instance_mapping in order to make it clear that the method is for API formatting, not for internal use. --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 65f18ddbf..9b0ec2fde 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -237,7 +237,7 @@ class CloudController(object): state = 'available' return image['properties'].get('image_state', state) - def _get_instance_mapping(self, ctxt, instance_ref): + def _format_instance_mapping(self, ctxt, instance_ref): root_device_name = instance_ref['root_device_name'] if root_device_name is None: return _DEFAULT_MAPPINGS @@ -287,7 +287,7 @@ class CloudController(object): security_groups = db.security_group_get_by_instance(ctxt, instance_ref['id']) security_groups = [x['name'] for x in security_groups] - mappings = self._get_instance_mapping(ctxt, instance_ref) + mappings = self._format_instance_mapping(ctxt, instance_ref) data = { 'user-data': self._format_user_data(instance_ref), 'meta-data': { -- cgit From 45c3c01f69e1f13ced70942e6c8369098a307c48 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 29 Jul 2011 01:54:19 -0400 Subject: Added xml schema validation for extensions resources. Added corresponding xml schemas. Added lxml dep, which is needed for doing xml schema validation. --- nova/api/openstack/extensions.py | 31 +- nova/api/openstack/schemas/atom-link.rng | 141 ++++++ nova/api/openstack/schemas/atom.rng | 597 +++++++++++++++++++++++++ nova/api/openstack/schemas/v1.1/extension.rng | 11 + nova/api/openstack/schemas/v1.1/extensions.rng | 6 + 5 files changed, 772 insertions(+), 14 deletions(-) create mode 100644 nova/api/openstack/schemas/atom-link.rng create mode 100644 nova/api/openstack/schemas/atom.rng create mode 100644 nova/api/openstack/schemas/v1.1/extension.rng create mode 100644 nova/api/openstack/schemas/v1.1/extensions.rng (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..6188e274d 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -23,7 +23,7 @@ import sys import routes import webob.dec import webob.exc -from xml.etree import ElementTree +from lxml import etree from nova import exception from nova import flags @@ -32,6 +32,7 @@ from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil LOG = logging.getLogger('extensions') @@ -470,36 +471,38 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + def show(self, ext_dict): - ext = self._create_ext_elem(ext_dict['extension']) + ext = etree.Element('extension', nsmap=self.NSMAP) + self._populate_ext(ext, ext_dict['extension']) return self._to_xml(ext) def index(self, exts_dict): - exts = ElementTree.Element('extensions') + exts = etree.Element('extensions', nsmap=self.NSMAP) for ext_dict in exts_dict['extensions']: - exts.append(self._create_ext_elem(ext_dict)) + ext = etree.SubElement(exts, 'extension') + self._populate_ext(ext, ext_dict) return self._to_xml(exts) - def _create_ext_elem(self, ext_dict): - """Create an extension xml element from a dict.""" - ext_elem = ElementTree.Element('extension') + def _populate_ext(self, ext_elem, ext_dict): + """Populate an extension xml element from a dict.""" + ext_elem.set('name', ext_dict['name']) ext_elem.set('namespace', ext_dict['namespace']) ext_elem.set('alias', ext_dict['alias']) ext_elem.set('updated', ext_dict['updated']) - desc = ElementTree.Element('description') + desc = etree.Element('description') desc.text = ext_dict['description'] ext_elem.append(desc) for link in ext_dict.get('links', []): - elem = ElementTree.Element('atom:link') + elem = etree.SubElement(ext_elem, '{%s}link' % xmlutil.XMLNS_ATOM) elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) - ext_elem.append(elem) return ext_elem def _to_xml(self, root): - """Convert the xml tree object to an xml string.""" - root.set('xmlns', wsgi.XMLNS_V11) - root.set('xmlns:atom', wsgi.XMLNS_ATOM) - return ElementTree.tostring(root, encoding='UTF-8') + """Convert the xml object to an xml string.""" + + return etree.tostring(root, encoding='UTF-8') diff --git a/nova/api/openstack/schemas/atom-link.rng b/nova/api/openstack/schemas/atom-link.rng new file mode 100644 index 000000000..edba5eee6 --- /dev/null +++ b/nova/api/openstack/schemas/atom-link.rng @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + [^:]* + + + + + + .+/.+ + + + + + + [A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})* + + + + + + + + + + + + xml:base + xml:lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/atom.rng b/nova/api/openstack/schemas/atom.rng new file mode 100644 index 000000000..c2df4e410 --- /dev/null +++ b/nova/api/openstack/schemas/atom.rng @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:feed must have an atom:author unless all of its atom:entry children have an atom:author. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:entry must have at least one atom:link element with a rel attribute of 'alternate' or an atom:content. + + + An atom:entry must have an atom:author if its feed does not. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + [^:]* + + + + + + .+/.+ + + + + + + [A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})* + + + + + + + + + + .+@.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xml:base + xml:lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/extension.rng b/nova/api/openstack/schemas/v1.1/extension.rng new file mode 100644 index 000000000..336659755 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/extension.rng @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/extensions.rng b/nova/api/openstack/schemas/v1.1/extensions.rng new file mode 100644 index 000000000..4d8bff646 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/extensions.rng @@ -0,0 +1,6 @@ + + + + + -- cgit From 85795ff1f8b6a0ff3de634828208d6debd91692f Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 1 Aug 2011 21:06:47 +0000 Subject: Added option for rebooting or shutting down a host. --- nova/api/openstack/contrib/hosts.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 55e57e1a4..b6a4bdb77 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,6 +78,12 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) + elif key == "powerstate": + if val in ("reboot", "shutdown"): + return self._set_powerstate(req, id, val) + else: + explanation = _("Invalid powerstate: '%s'") % raw_val + raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -91,6 +97,15 @@ class HostController(object): enabled=enabled) return {"host": host, "status": result} + def _set_powerstate(self, req, host, state): + """Reboots or shuts down the host.""" + context = req.environ['nova.context'] + LOG.audit(_("Changing powerstate of host %(host)s to %(state)s.") + % locals()) + result = self.compute_api.set_host_powerstate(context, host=host, + state=state) + return {"host": host, "powerstate": result} + class Hosts(extensions.ExtensionDescriptor): def get_name(self): -- cgit From f06dee2b82bd658a57736d94974f431976085400 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 19:02:40 +0000 Subject: Fixed several typos --- nova/api/openstack/contrib/hosts.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 94fba910c..2d9d494b9 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -85,7 +85,7 @@ class HostController(object): # 'startup' option to start up a host, but this is not # technically feasible now, as we run the host on the # XenServer box. - msg = _("Host startup on XenServer is not supported.")) + msg = _("Host startup on XenServer is not supported.") raise webob.exc.HTTPBadRequest(explanation=msg) elif val in ("reboot", "shutdown"): return self._set_powerstate(req, id, val) @@ -106,8 +106,7 @@ class HostController(object): return {"host": host, "status": result} def _set_powerstate(self, req, host, state): - """Reboots or shuts down the host. - """ + """Reboots or shuts down the host.""" context = req.environ['nova.context'] result = self.compute_api.set_host_powerstate(context, host=host, state=state) -- cgit From 1d3d1d5fb552f2dc80c39ad15d89d59bfc7f873a Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 21:11:12 +0000 Subject: Minor test fixes --- nova/api/openstack/contrib/hosts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index e9f82c75b..c90a889d5 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,7 +78,7 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) - elif key == "powerstate": + elif key == "power_state": if val == "startup": # The only valid values for 'state' are 'reboot' or # 'shutdown'. For completeness' sake there is the @@ -113,7 +113,7 @@ class HostController(object): context = req.environ['nova.context'] result = self.compute_api.set_host_powerstate(context, host=host, state=state) - return {"host": host, "powerstate": result} + return {"host": host, "power_state": result} class Hosts(extensions.ExtensionDescriptor): -- cgit From 14e8257af4624fa5b056a1b0e94d1b584e080ce9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 22:48:47 +0000 Subject: Added check for --allow-admin-api to the host API extension code. --- nova/api/openstack/contrib/hosts.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index c90a889d5..78ba66771 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -133,6 +133,11 @@ class Hosts(extensions.ExtensionDescriptor): return "2011-06-29T00:00:00+00:00" def get_resources(self): - resources = [extensions.ResourceExtension('os-hosts', HostController(), - collection_actions={'update': 'PUT'}, member_actions={})] + resources = [] + # If we are not in an admin env, don't add the resource. Regular users + # shouldn't have access to the host. + if FLAGS.allow_admin_api: + resources = [extensions.ResourceExtension('os-hosts', + HostController(), collection_actions={'update': 'PUT'}, + member_actions={})] return resources -- cgit From 7b69ef4fe1e4aabcf44789455b96492b168ad6f5 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 3 Aug 2011 01:32:08 +0000 Subject: Removed trailing whitespace that somehow made it into trunk. --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 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 a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ class CreateInstanceHelper(object): image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - + if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: -- cgit From 75b110aa451382cce94f10a392597b40df97839c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 20:49:21 +0000 Subject: Changed all references to 'power state' to 'power action' as requested by review. --- nova/api/openstack/contrib/hosts.py | 43 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 78ba66771..09adbe2f4 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -70,7 +70,7 @@ class HostController(object): key = raw_key.lower().strip() val = raw_val.lower().strip() # NOTE: (dabo) Right now only 'status' can be set, but other - # actions may follow. + # settings may follow. if key == "status": if val[:6] in ("enable", "disabl"): return self._set_enabled_status(req, id, @@ -78,20 +78,6 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) - elif key == "power_state": - if val == "startup": - # The only valid values for 'state' are 'reboot' or - # 'shutdown'. For completeness' sake there is the - # 'startup' option to start up a host, but this is not - # technically feasible now, as we run the host on the - # XenServer box. - msg = _("Host startup on XenServer is not supported.") - raise webob.exc.HTTPBadRequest(explanation=msg) - elif val in ("reboot", "shutdown"): - return self._set_powerstate(req, id, val) - else: - explanation = _("Invalid powerstate: '%s'") % raw_val - raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -108,12 +94,28 @@ class HostController(object): raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result} - def _set_powerstate(self, req, host, state): + def _host_power_action(self, req, host, action): """Reboots or shuts down the host.""" context = req.environ['nova.context'] - result = self.compute_api.set_host_powerstate(context, host=host, - state=state) - return {"host": host, "power_state": result} + result = self.compute_api.host_power_action(context, host=host, + action=action) + return {"host": host, "power_action": result} + + def startup(self, req, id): + """The only valid values for 'action' are 'reboot' or + 'shutdown'. For completeness' sake there is the + 'startup' option to start up a host, but this is not + technically feasible now, as we run the host on the + XenServer box. + """ + msg = _("Host startup on XenServer is not supported.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + def shutdown(self, req, id): + return self._host_power_action(req, host=id, action="shutdown") + + def reboot(self, req, id): + return self._host_power_action(req, host=id, action="reboot") class Hosts(extensions.ExtensionDescriptor): @@ -139,5 +141,6 @@ class Hosts(extensions.ExtensionDescriptor): if FLAGS.allow_admin_api: resources = [extensions.ResourceExtension('os-hosts', HostController(), collection_actions={'update': 'PUT'}, - member_actions={})] + member_actions={"startup": "GET", "shutdown": "GET", + "reboot": "GET"})] return resources -- cgit From dcac4bc6c7be9832704e37cca7ce815e083974f5 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 20:55:56 +0000 Subject: Added admin-only decorator --- nova/api/openstack/contrib/admin_only.py | 30 ++++++++++++++++++++++++++++++ nova/api/openstack/contrib/hosts.py | 14 ++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 nova/api/openstack/contrib/admin_only.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/admin_only.py b/nova/api/openstack/contrib/admin_only.py new file mode 100644 index 000000000..e821c9e1f --- /dev/null +++ b/nova/api/openstack/contrib/admin_only.py @@ -0,0 +1,30 @@ +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Decorator for limiting extensions that should be admin-only.""" + +from functools import wraps +from nova import flags +FLAGS = flags.FLAGS + + +def admin_only(fnc): + @wraps(fnc) + def _wrapped(self, *args, **kwargs): + if FLAGS.allow_admin_api: + return fnc(self, *args, **kwargs) + return [] + _wrapped.func_name = fnc.func_name + return _wrapped diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 09adbe2f4..cdf8760d5 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -24,6 +24,7 @@ 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.contrib import admin_only from nova.scheduler import api as scheduler_api @@ -134,13 +135,10 @@ class Hosts(extensions.ExtensionDescriptor): def get_updated(self): return "2011-06-29T00:00:00+00:00" + @admin_only.admin_only def get_resources(self): - resources = [] - # If we are not in an admin env, don't add the resource. Regular users - # shouldn't have access to the host. - if FLAGS.allow_admin_api: - resources = [extensions.ResourceExtension('os-hosts', - HostController(), collection_actions={'update': 'PUT'}, - member_actions={"startup": "GET", "shutdown": "GET", - "reboot": "GET"})] + resources = [extensions.ResourceExtension('os-hosts', + HostController(), collection_actions={'update': 'PUT'}, + member_actions={"startup": "GET", "shutdown": "GET", + "reboot": "GET"})] return resources -- cgit From 79e51d7b138948eddd307747c517be9ad1aa67d1 Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 01:07:53 +0000 Subject: Adding missing module xmlutil --- nova/api/openstack/xmlutil.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 nova/api/openstack/xmlutil.py (limited to 'nova/api') diff --git a/nova/api/openstack/xmlutil.py b/nova/api/openstack/xmlutil.py new file mode 100644 index 000000000..97ad90ada --- /dev/null +++ b/nova/api/openstack/xmlutil.py @@ -0,0 +1,37 @@ +# 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 os.path + +from lxml import etree + +from nova import utils + + +XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' +XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' +XMLNS_ATOM = 'http://www.w3.org/2005/Atom' + + +def validate_schema(xml, schema_name): + if type(xml) is str: + xml = etree.fromstring(xml) + schema_path = os.path.join(utils.novadir(), + 'nova/api/openstack/schemas/v1.1/%s.rng' % schema_name) + schema_doc = etree.parse(schema_path) + relaxng = etree.RelaxNG(schema_doc) + relaxng.assertValid(xml) -- cgit From ab1ba7cbcffc92c2c82c468fb0a2a81f93db3f85 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 06:01:55 -0700 Subject: fixed up zones controller to properly work with 1.1 --- nova/api/openstack/zones.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index f7fd87bcd..a2bf267ed 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -166,7 +166,7 @@ class Controller(object): return self.helper._get_server_admin_password_old_style(server) -class ControllerV11(object): +class ControllerV11(Controller): """Controller for 1.1 Zone resources.""" def _get_server_admin_password(self, server): -- cgit From e3433605d77492a58916d2e131eb0701baf849fa Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 07:19:35 -0700 Subject: pep8 violations sneaking into trunk? --- nova/api/direct.py | 1 + nova/api/openstack/common.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 139c46d63..fdd2943d2 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,6 +48,7 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} + def register_service(path, handle): """Register a service handle at a given path. diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 715b9e4a4..75e862630 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -154,7 +154,8 @@ def remove_version_from_href(href): """ parsed_url = urlparse.urlsplit(href) - new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, + count=1) if new_path == parsed_url.path: msg = _('href %s does not contain version') % href -- cgit From a30856cd5a7358772d47c3877dd01d1078ffe472 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 5 Aug 2011 20:05:33 -0400 Subject: Update the OSAPI v1.1 server 'createImage' and 'createBackup' actions to limit the number of image metadata items based on the configured quota.allowed_metadata_items that is set. --- nova/api/openstack/common.py | 14 +++++++++++++- nova/api/openstack/image_metadata.py | 16 +++------------- nova/api/openstack/servers.py | 6 ++++-- 3 files changed, 20 insertions(+), 16 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 715b9e4a4..a4cdbda76 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -24,6 +24,7 @@ import webob from nova import exception from nova import flags from nova import log as logging +from nova import quota from nova.api.openstack import wsgi @@ -154,7 +155,8 @@ def remove_version_from_href(href): """ parsed_url = urlparse.urlsplit(href) - new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, + count=1) if new_path == parsed_url.path: msg = _('href %s does not contain version') % href @@ -191,6 +193,16 @@ def get_version_from_href(href): return version +def check_img_metadata_quota_limit(context, metadata): + if metadata is None: + return + num_metadata = len(metadata) + quota_metadata = quota.allowed_metadata_items(context, num_metadata) + if quota_metadata < num_metadata: + expl = _("Image metadata limit exceeded") + raise webob.exc.HTTPBadRequest(explanation=expl) + + class MetadataXMLDeserializer(wsgi.XMLDeserializer): def extract_metadata(self, metadata_node): diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index aaf64a123..4d615ea96 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -19,7 +19,6 @@ from webob import exc from nova import flags from nova import image -from nova import quota from nova import utils from nova.api.openstack import common from nova.api.openstack import wsgi @@ -40,15 +39,6 @@ class Controller(object): metadata = image.get('properties', {}) return metadata - def _check_quota_limit(self, context, metadata): - if metadata is None: - return - num_metadata = len(metadata) - quota_metadata = quota.allowed_metadata_items(context, num_metadata) - if quota_metadata < num_metadata: - expl = _("Image metadata limit exceeded") - raise exc.HTTPBadRequest(explanation=expl) - def index(self, req, image_id): """Returns the list of metadata for a given instance""" context = req.environ['nova.context'] @@ -70,7 +60,7 @@ class Controller(object): if 'metadata' in body: for key, value in body['metadata'].iteritems(): metadata[key] = value - self._check_quota_limit(context, metadata) + common.check_img_metadata_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) @@ -93,7 +83,7 @@ class Controller(object): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) metadata[id] = meta[id] - self._check_quota_limit(context, metadata) + common.check_img_metadata_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) return dict(meta=meta) @@ -102,7 +92,7 @@ class Controller(object): context = req.environ['nova.context'] img = self.image_service.show(context, image_id) metadata = body.get('metadata', {}) - self._check_quota_limit(context, metadata) + common.check_img_metadata_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3930982dc..6936864df 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -218,13 +218,14 @@ class Controller(object): props = {'instance_ref': server_ref} metadata = entity.get('metadata', {}) + context = req.environ["nova.context"] + common.check_img_metadata_quota_limit(context, metadata) try: props.update(metadata) except ValueError: msg = _("Invalid metadata") raise webob.exc.HTTPBadRequest(explanation=msg) - context = req.environ["nova.context"] image = self.compute_api.backup(context, instance_id, image_name, @@ -702,13 +703,14 @@ class ControllerV11(Controller): props = {'instance_ref': server_ref} metadata = entity.get('metadata', {}) + context = req.environ['nova.context'] + common.check_img_metadata_quota_limit(context, metadata) try: props.update(metadata) except ValueError: msg = _("Invalid metadata") raise webob.exc.HTTPBadRequest(explanation=msg) - context = req.environ['nova.context'] image = self.compute_api.snapshot(context, instance_id, image_name, -- cgit From 8c75de3188fdbec6456fcf7071b6b08b9d1a0d40 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 7 Aug 2011 16:40:07 -0400 Subject: Set image progress to 100 if the image is active. --- nova/api/openstack/views/images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 873ce212a..8539fbcbf 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -77,7 +77,9 @@ class ViewBuilder(object): "status": image_obj.get("status"), }) - if image["status"] == "SAVING": + if image["status"] == "ACTIVE": + image["progress"] = 100 + else: image["progress"] = 0 return image -- cgit From 309e49873fd2535fa64b242aea254b72b5cbb4a9 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Sun, 7 Aug 2011 22:05:01 -0400 Subject: Adding __init__.py files --- nova/api/openstack/schemas/__init__.py | 0 nova/api/openstack/schemas/v1.1/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 nova/api/openstack/schemas/__init__.py create mode 100644 nova/api/openstack/schemas/v1.1/__init__.py (limited to 'nova/api') diff --git a/nova/api/openstack/schemas/__init__.py b/nova/api/openstack/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/openstack/schemas/v1.1/__init__.py b/nova/api/openstack/schemas/v1.1/__init__.py new file mode 100644 index 000000000..e69de29bb -- cgit From e4ee8b54d0e840050357902b78f7e48013be9096 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 8 Aug 2011 10:12:01 -0400 Subject: upper() is even better. --- nova/api/openstack/views/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 8539fbcbf..912303d14 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -77,7 +77,7 @@ class ViewBuilder(object): "status": image_obj.get("status"), }) - if image["status"] == "ACTIVE": + if image["status"].upper() == "ACTIVE": image["progress"] = 100 else: image["progress"] = 0 -- cgit From b1a503053cb8cbeb1a4ab18e650b49cc4da15e23 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:19:53 +0000 Subject: Moved the restriction on host startup to the xenapi layer.: --- nova/api/openstack/contrib/hosts.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index cdf8760d5..d5bd3166b 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -96,21 +96,17 @@ class HostController(object): return {"host": host, "status": result} def _host_power_action(self, req, host, action): - """Reboots or shuts down the host.""" + """Reboots, shuts down or powers up the host.""" context = req.environ['nova.context'] - result = self.compute_api.host_power_action(context, host=host, - action=action) + try: + result = self.compute_api.host_power_action(context, host=host, + action=action) + except NotImplementedError as e: + raise webob.exc.HTTPBadRequest(explanation=e.msg) return {"host": host, "power_action": result} def startup(self, req, id): - """The only valid values for 'action' are 'reboot' or - 'shutdown'. For completeness' sake there is the - 'startup' option to start up a host, but this is not - technically feasible now, as we run the host on the - XenServer box. - """ - msg = _("Host startup on XenServer is not supported.") - raise webob.exc.HTTPBadRequest(explanation=msg) + return self._host_power_action(req, host=id, action="startup") def shutdown(self, req, id): return self._host_power_action(req, host=id, action="shutdown") -- cgit From 973032959ea4b1300cb68f767885dbd3226bebd9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:42:18 +0000 Subject: Fixed some typos from the last refactoring --- nova/api/openstack/contrib/hosts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index d5bd3166b..ecaa365b7 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -133,7 +133,7 @@ class Hosts(extensions.ExtensionDescriptor): @admin_only.admin_only def get_resources(self): - resources = [extensions.ResourceExtension('os-hosts', + resources = [extensions.ResourceExtension('os-hosts', HostController(), collection_actions={'update': 'PUT'}, member_actions={"startup": "GET", "shutdown": "GET", "reboot": "GET"})] -- cgit From 61cf3721ce94d7f2458e4e469cbee3333f954588 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 8 Aug 2011 16:38:14 -0400 Subject: cleaning up instance metadata api code --- nova/api/openstack/server_metadata.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index b0b014f86..969769729 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -57,16 +57,7 @@ class Controller(object): context = req.environ['nova.context'] - try: - self.compute_api.update_or_create_instance_metadata(context, - server_id, - metadata) - except exception.InstanceNotFound: - msg = _('Server does not exist') - raise exc.HTTPNotFound(explanation=msg) - - except quota.QuotaError as error: - self._handle_quota_error(error) + self._update_instance_metadata(context, server_id, metadata, False) return body @@ -88,7 +79,7 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=expl) context = req.environ['nova.context'] - self._set_instance_metadata(context, server_id, meta_item) + self._update_instance_metadata(context, server_id, meta_item, False) return {'meta': {id: meta_value}} @@ -100,20 +91,23 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=expl) context = req.environ['nova.context'] - self._set_instance_metadata(context, server_id, metadata) + self._update_instance_metadata(context, server_id, metadata, True) return {'metadata': metadata} - def _set_instance_metadata(self, context, server_id, metadata): + def _update_instance_metadata(self, context, server_id, metadata, + delete=False): try: - self.compute_api.update_or_create_instance_metadata(context, - server_id, - metadata) + self.compute_api.update_instance_metadata(context, + server_id, + metadata, + delete) + except exception.InstanceNotFound: msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) - except ValueError: + except (ValueError, AttributeError): msg = _("Malformed request body") raise exc.HTTPBadRequest(explanation=msg) -- cgit From 4de24a4d44040ba38a474cd789b95a2b59d494ff Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 8 Aug 2011 17:33:03 -0400 Subject: making server metadata work functionally --- nova/api/openstack/server_metadata.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 969769729..ed90be0c9 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -23,6 +23,10 @@ from nova.api.openstack import wsgi from nova import exception from nova import quota +from nova import log as logging + +LOG = logging.getLogger("nova.api.openstack.server_metadata") + class Controller(object): """ The server metadata API controller for the Openstack API """ @@ -69,19 +73,19 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=expl) try: - meta_value = meta_item.pop(id) + meta_value = meta_item[id] except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(meta_item) > 0: + if len(meta_item) > 1: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) context = req.environ['nova.context'] self._update_instance_metadata(context, server_id, meta_item, False) - return {'meta': {id: meta_value}} + return {'meta': meta_item} def update_all(self, req, server_id, body): try: @@ -107,8 +111,8 @@ class Controller(object): msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) - except (ValueError, AttributeError): - msg = _("Malformed request body") + except (ValueError, AttributeError), ex: + msg = _("Malformed request body: %s") % (str(ex),) raise exc.HTTPBadRequest(explanation=msg) except quota.QuotaError as error: @@ -132,12 +136,12 @@ class Controller(object): metadata = self._get_metadata(context, server_id) try: - meta_key = metadata[id] + meta_value = metadata[id] except KeyError: msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) - self.compute_api.delete_instance_metadata(context, server_id, meta_key) + self.compute_api.delete_instance_metadata(context, server_id, id) def _handle_quota_error(self, error): """Reraise quota errors as api-specific http exceptions.""" -- cgit From 607d919420913969c90cedfba8857f07fc355c5e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 8 Aug 2011 17:44:58 -0400 Subject: removing log lines --- nova/api/openstack/server_metadata.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index ed90be0c9..a10c48156 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -23,10 +23,6 @@ from nova.api.openstack import wsgi from nova import exception from nova import quota -from nova import log as logging - -LOG = logging.getLogger("nova.api.openstack.server_metadata") - class Controller(object): """ The server metadata API controller for the Openstack API """ @@ -112,7 +108,7 @@ class Controller(object): raise exc.HTTPNotFound(explanation=msg) except (ValueError, AttributeError), ex: - msg = _("Malformed request body: %s") % (str(ex),) + msg = _("Malformed request body") raise exc.HTTPBadRequest(explanation=msg) except quota.QuotaError as error: -- cgit From 543a783cefc3b34fa4a5d4ae5b9034090666d182 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 8 Aug 2011 20:23:15 -0400 Subject: Fixing a bug in nova.utils.novadir() --- nova/api/openstack/schemas/__init__.py | 0 nova/api/openstack/schemas/v1.1/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 nova/api/openstack/schemas/__init__.py delete mode 100644 nova/api/openstack/schemas/v1.1/__init__.py (limited to 'nova/api') diff --git a/nova/api/openstack/schemas/__init__.py b/nova/api/openstack/schemas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/api/openstack/schemas/v1.1/__init__.py b/nova/api/openstack/schemas/v1.1/__init__.py deleted file mode 100644 index e69de29bb..000000000 -- cgit From d7880c2a0ba1d4285edb33208e8a94a8e9f15a21 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 9 Aug 2011 00:27:28 -0400 Subject: changing server create response to 202 --- nova/api/openstack/servers.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f1a27a98c..63e71b3eb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -743,6 +743,9 @@ class ControllerV11(Controller): class HeadersSerializer(wsgi.ResponseHeadersSerializer): + def create(self, response, data): + response.status_int = 202 + def delete(self, response, data): response.status_int = 204 -- cgit From ed4a3b33647d3cbf5b1733596c1e180078e23cb0 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 9 Aug 2011 10:29:07 -0400 Subject: updating tests; fixing create output; review fixes --- nova/api/openstack/server_metadata.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index a10c48156..97a43fccf 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -57,9 +57,12 @@ class Controller(object): context = req.environ['nova.context'] - self._update_instance_metadata(context, server_id, metadata, False) + new_metadata = self._update_instance_metadata(context, + server_id, + metadata, + False) - return body + return {'metadata': new_metadata} def update(self, req, server_id, id, body): try: @@ -91,23 +94,26 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=expl) context = req.environ['nova.context'] - self._update_instance_metadata(context, server_id, metadata, True) + new_metadata = self._update_instance_metadata(context, + server_id, + metadata, + True) - return {'metadata': metadata} + return {'metadata': new_metadata} def _update_instance_metadata(self, context, server_id, metadata, delete=False): try: - self.compute_api.update_instance_metadata(context, - server_id, - metadata, - delete) + return self.compute_api.update_instance_metadata(context, + server_id, + metadata, + delete) except exception.InstanceNotFound: msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) - except (ValueError, AttributeError), ex: + except (ValueError, AttributeError): msg = _("Malformed request body") raise exc.HTTPBadRequest(explanation=msg) -- cgit From d72e36d63b1aefe7731d5c832c2b2fa52227407c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 9 Aug 2011 11:10:14 -0400 Subject: making usage of 'delete' argument more clear --- nova/api/openstack/server_metadata.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 97a43fccf..2b235f79a 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -60,7 +60,7 @@ class Controller(object): new_metadata = self._update_instance_metadata(context, server_id, metadata, - False) + delete=False) return {'metadata': new_metadata} @@ -82,7 +82,10 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=expl) context = req.environ['nova.context'] - self._update_instance_metadata(context, server_id, meta_item, False) + self._update_instance_metadata(context, + server_id, + meta_item, + delete=False) return {'meta': meta_item} @@ -97,7 +100,7 @@ class Controller(object): new_metadata = self._update_instance_metadata(context, server_id, metadata, - True) + delete=True) return {'metadata': new_metadata} -- cgit From 09772f5bf3140a6f4cbaace50ead8d25a874cbb0 Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Fri, 5 Aug 2011 14:37:44 -0700 Subject: If ip is deallocated from project, but attached to a fixed ip, it is now detached --- nova/api/openstack/contrib/floating_ips.py | 7 ++++++- 1 file changed, 6 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 3d8049324..616388e80 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -97,8 +97,13 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] - ip = self.network_api.get_floating_ip(context, id) + try: + if 'fixed_ip' in ip: + self.disassociate(req, id, '') + except: + pass + self.network_api.release_floating_ip(context, address=ip) return {'released': { -- cgit From ccea6c91b2314311587466d67d20f1583ddba1ee Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Fri, 5 Aug 2011 15:28:10 -0700 Subject: adding logging to exception in delete method --- nova/api/openstack/contrib/floating_ips.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 616388e80..49ab88bb6 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -24,6 +24,9 @@ from nova.api.openstack import faults from nova.api.openstack import extensions +LOG = logging.getLogger('nova.api.openstack.contrib.floating_ips') + + def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} @@ -98,11 +101,12 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) + try: if 'fixed_ip' in ip: self.disassociate(req, id, '') - except: - pass + except Exception, e: + LOG.exception(_("Error disassociating fixed_ip %s"), e) self.network_api.release_floating_ip(context, address=ip) -- cgit From fe7f229c8ad91b1ae9187b8c541fdefd535eed9b Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Fri, 5 Aug 2011 16:20:53 -0700 Subject: moving try/except block, and changing syntax of except statement --- nova/api/openstack/contrib/floating_ips.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 49ab88bb6..996a42abe 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -101,12 +101,12 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) - - try: - if 'fixed_ip' in ip: + + if 'fixed_ip' in ip: + try: self.disassociate(req, id, '') - except Exception, e: - LOG.exception(_("Error disassociating fixed_ip %s"), e) + except Exception as e: + LOG.exception(_("Error disassociating fixed_ip %s"), e) self.network_api.release_floating_ip(context, address=ip) -- cgit From c600b2cf3697fc3587fe5519fda8dd4b82d67234 Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Mon, 8 Aug 2011 12:10:14 -0700 Subject: adding forgotten import for logging --- nova/api/openstack/contrib/floating_ips.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 996a42abe..52c9c6cf9 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -18,6 +18,7 @@ from webob import exc from nova import exception +from nova import log as logging from nova import network from nova import rpc from nova.api.openstack import faults -- cgit