From 7a8ecdc03f838184b2e6eeac62d7f57ddc64967b Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 8 Jul 2011 01:39:58 -0700 Subject: start of re-work of compute/api's 'get_all' to handle more search options --- nova/compute/api.py | 83 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 24 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index b0eedcd64..42ccc9f9e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -622,34 +622,71 @@ class API(base.Base): """ return self.get(context, instance_id) - def get_all(self, context, project_id=None, reservation_id=None, - fixed_ip=None, recurse_zones=False): + def _get_all_by_reservation_id(self, context, search_opts): + search_opts['recurse_zones'] = True + return self.db.instance_get_all_by_reservation( + context, reservation_id) + + def _get_all_by_fixed_ip(self, context, search_opts): + try: + instances = self.db.fixed_ip_get_instance(context, fixed_ip) + except exception.FloatingIpNotFound, e: + instances = None + return instances + + def _get_all_by_project_id(self, context, search_opts): + return self.db.instance_get_all_by_project( + context, project_id) + + def _get_all_by_ip(self, context, search_opts): + pass + + def _get_all_by_ip6(self, context, search_opts): + pass + + def _get_all_by_name(self, context, search_opts): + pass + + def get_all(self, context, search_opts=None): """Get all instances filtered by one of the given parameters. If there is no filter and the context is an admin, it will retreive all instances in the system. """ - if reservation_id is not None: - recurse_zones = True - instances = self.db.instance_get_all_by_reservation( - context, reservation_id) - elif fixed_ip is not None: - try: - instances = self.db.fixed_ip_get_instance(context, fixed_ip) - except exception.FloatingIpNotFound, e: - if not recurse_zones: + if search_opts is None: + search_opts = {} + + exclusive_opts = ['reservation_id', + 'project_id', + 'fixed_ip', + 'ip', + 'ip6', + 'name'] + + # See if a valud search option was passed in. + # Ignore unknown search options for possible forward compatability. + # Raise an exception if more than 1 search option is specified + option = None + for k in exclusive_opts.iterkeys(): + v = search_opts.get(k, None) + if v: + if option is None: + option = k + else: raise - instances = None - elif project_id or not context.is_admin: - if not context.project: + + if option: + method_name = '_get_all_by_%s' % option + method = getattr(self, method_name, None) + instances = method(context, search_opts) + elif not context.is_admin: + if context.project: + instances = self.db.instance_get_all_by_project( + context, context.project_id) + else: instances = self.db.instance_get_all_by_user( context, context.user_id) - else: - if project_id is None: - project_id = context.project_id - instances = self.db.instance_get_all_by_project( - context, project_id) else: instances = self.db.instance_get_all(context) @@ -658,17 +695,15 @@ class API(base.Base): elif not isinstance(instances, list): instances = [instances] - if not recurse_zones: + if not search_opts.get('recurse_zones', False): return instances + # Recurse zones. Need admin context for this. admin_context = context.elevated() children = scheduler_api.call_zone_method(admin_context, "list", novaclient_collection_name="servers", - reservation_id=reservation_id, - project_id=project_id, - fixed_ip=fixed_ip, - recurse_zones=True) + **search_opts) for zone, servers in children: for server in servers: -- cgit 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 ++-- nova/compute/api.py | 75 ++++++++----- nova/db/api.py | 44 ++++++-- nova/db/sqlalchemy/api.py | 239 +++++++++++++++++++++++++++++++----------- nova/db/sqlalchemy/models.py | 1 + 6 files changed, 292 insertions(+), 110 deletions(-) (limited to 'nova') 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'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 42ccc9f9e..e80e52566 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -625,27 +625,34 @@ class API(base.Base): def _get_all_by_reservation_id(self, context, search_opts): search_opts['recurse_zones'] = True return self.db.instance_get_all_by_reservation( - context, reservation_id) + context, search_opts['reservation_id']) + + def _get_all_by_project_id(self, context, search_opts): + return self.db.instance_get_all_by_project( + context, search_opts['project_id']) def _get_all_by_fixed_ip(self, context, search_opts): + fixed_ip = search_opts['fixed_ip'] try: - instances = self.db.fixed_ip_get_instance(context, fixed_ip) - except exception.FloatingIpNotFound, e: - instances = None + instances = self.db.instance_get_by_fixed_ip(context, fixed_ip) + except exception.FixedIpNotFound, e: + raise + if not instances: + raise exception.FixedIpNotFoundForAddress(address=fixed_ip) return instances - def _get_all_by_project_id(self, context, search_opts): - return self.db.instance_get_all_by_project( - context, project_id) def _get_all_by_ip(self, context, search_opts): - pass + return self.db.instance_get_all_by_ip_regexp( + context, search_opts['ip']) def _get_all_by_ip6(self, context, search_opts): - pass + return self.db.instance_get_all_by_ipv6_regexp( + context, search_opts['ip6']) - def _get_all_by_name(self, context, search_opts): - pass + def _get_all_by_column(self, context, column, search_opts): + return self.db.instance_get_all_by_column_regexp( + context, column, search_opts[column]) def get_all(self, context, search_opts=None): """Get all instances filtered by one of the given parameters. @@ -657,29 +664,45 @@ class API(base.Base): if search_opts is None: search_opts = {} + LOG.debug(_("Searching by: %s") % str(search_opts)) + + # Columns we can do a generic search on + search_columns = ['display_name', + 'server_name'] + + # Options that are mutually exclusive exclusive_opts = ['reservation_id', 'project_id', 'fixed_ip', 'ip', - 'ip6', - 'name'] + 'ip6'] + search_columns - # See if a valud search option was passed in. + # See if a valid search option was passed in. # Ignore unknown search options for possible forward compatability. # Raise an exception if more than 1 search option is specified - option = None - for k in exclusive_opts.iterkeys(): - v = search_opts.get(k, None) + found_opt = None + for opt in exclusive_opts: + v = search_opts.get(opt, None) if v: - if option is None: - option = k + if found_opt is None: + found_opt = opt else: - raise - - if option: - method_name = '_get_all_by_%s' % option - method = getattr(self, method_name, None) - instances = method(context, search_opts) + LOG.error(_("More than 1 mutually exclusive " + "search option specified (%(found_opt)s and " + "%(opt)s were both specified") % locals()) + raise exception.InvalidInput(reason= + _("More than 1 mutually exclusive " + "search option specified (%(found_opt)s and " + "%(opt)s were both specified") % locals()) + + if found_opt: + if found_opt in search_columns: + instances = self._get_all_by_column(context, + found_opt, search_opts) + else: + method_name = '_get_all_by_%s' % found_opt + method = getattr(self, method_name, None) + instances = method(context, search_opts) elif not context.is_admin: if context.project: instances = self.db.instance_get_all_by_project( @@ -703,7 +726,7 @@ class API(base.Base): children = scheduler_api.call_zone_method(admin_context, "list", novaclient_collection_name="servers", - **search_opts) + search_opts=search_opts) for zone, servers in children: for server in servers: diff --git a/nova/db/api.py b/nova/db/api.py index b7c5700e5..c0f49d98d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -381,15 +381,6 @@ def fixed_ip_get_by_virtual_interface(context, vif_id): return IMPL.fixed_ip_get_by_virtual_interface(context, vif_id) -def fixed_ip_get_instance(context, address): - """Get an instance for a fixed ip by address.""" - return IMPL.fixed_ip_get_instance(context, address) - - -def fixed_ip_get_instance_v6(context, address): - return IMPL.fixed_ip_get_instance_v6(context, address) - - def fixed_ip_get_network(context, address): """Get a network for a fixed ip by address.""" return IMPL.fixed_ip_get_network(context, address) @@ -515,10 +506,43 @@ def instance_get_all_by_host(context, host): def instance_get_all_by_reservation(context, reservation_id): - """Get all instance belonging to a reservation.""" + """Get all instances belonging to a reservation.""" return IMPL.instance_get_all_by_reservation(context, reservation_id) +def instance_get_by_fixed_ip(context, address): + """Get an instance for a fixed ip by address.""" + return IMPL.instance_get_by_fixed_ip(context, address) + + +def instance_get_by_fixed_ipv6(context, address): + """Get an instance for a fixed ip by IPv6 address.""" + return IMPL.instance_get_by_fixed_ipv6(context, address) + + +def instance_get_all_by_column_regexp(context, column, column_regexp): + """Get all instances by using regular expression matching against + a particular DB column + """ + return IMPL.instance_get_all_by_column_regexp(context, + column, + column_regexp) + + +def instance_get_all_by_ip_regexp(context, ip_regexp): + """Get all instances by using regular expression matching against + Floating and Fixed IP Addresses + """ + return IMPL.instance_get_all_by_ip_regexp(context, ip_regexp) + + +def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): + """Get all instances by using regular expression matching against + IPv6 Addresses + """ + return IMPL.instance_get_all_by_ipv6_regexp(context, ipv6_regexp) + + def instance_get_fixed_addresses(context, instance_id): """Get the fixed ip address of an instance.""" return IMPL.instance_get_fixed_addresses(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ffd009513..af2acbcb3 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -18,6 +18,7 @@ """ Implementation of SQLAlchemy backend. """ +import re import traceback import warnings @@ -797,28 +798,6 @@ def fixed_ip_get_by_virtual_interface(context, vif_id): return rv -@require_context -def fixed_ip_get_instance(context, address): - fixed_ip_ref = fixed_ip_get_by_address(context, address) - return fixed_ip_ref.instance - - -@require_context -def fixed_ip_get_instance_v6(context, address): - session = get_session() - - # convert IPv6 address to mac - mac = ipv6.to_mac(address) - - # get virtual interface - vif_ref = virtual_interface_get_by_address(context, mac) - - # look up instance based on instance_id from vif row - result = session.query(models.Instance).\ - filter_by(id=vif_ref['instance_id']) - return result - - @require_admin_context def fixed_ip_get_network(context, address): fixed_ip_ref = fixed_ip_get_by_address(context, address) @@ -1204,30 +1183,166 @@ def instance_get_all_by_project(context, project_id): @require_context def instance_get_all_by_reservation(context, reservation_id): session = get_session() + query = session.query(models.Instance).\ + filter_by(reservation_id=reservation_id).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')) if is_admin_context(context): - return session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(reservation_id=reservation_id).\ - filter_by(deleted=can_read_deleted(context)).\ - all() + return query.\ + filter_by(deleted=can_read_deleted(context)).\ + all() elif is_user_context(context): - return session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(project_id=context.project_id).\ - filter_by(reservation_id=reservation_id).\ - filter_by(deleted=False).\ - all() + return query.\ + filter_by(project_id=context.project_id).\ + filter_by(deleted=False).\ + all() + + +@require_context +def instance_get_by_fixed_ip(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + return fixed_ip_ref.instance + + +@require_context +def instance_get_by_fixed_ipv6(context, address): + session = get_session() + + # convert IPv6 address to mac + mac = ipv6.to_mac(address) + + # get virtual interface + vif_ref = virtual_interface_get_by_address(context, mac) + + # look up instance based on instance_id from vif row + result = session.query(models.Instance).\ + filter_by(id=vif_ref['instance_id']) + return result + + +@require_context +def instance_get_all_by_column_regexp(context, column, column_regexp): + """Get all instances by using regular expression matching against + a particular DB column + """ + session = get_session() + + # MySQL 'regexp' is not portable, so we must do our own matching. + # First... grab all Instances. + query = session.query(models.Instance).\ + options(joinedload('metadata')) + if is_admin_context(context): + all_instances = query.\ + filter_by(deleted=can_read_deleted(context)).\ + all() + elif is_user_context(context): + all_instances = query.\ + filter_by(project_id=context.project_id).\ + filter_by(deleted=False).\ + all() + else: + return [] + + if all_instances is None: + all_instances = [] + + # Now do the regexp matching + compiled_regexp = re.compile(column_regexp) + instances = [] + + for instance in all_instances: + v = getattr(instance, column) + if v and compiled_regexp.match(v): + instances.append(instance) + return instances + + +@require_context +def instance_get_all_by_ip_regexp(context, ip_regexp): + """Get all instances by using regular expression matching against + Floating and Fixed IP Addresses + """ + session = get_session() + + fixed_ip_query = session.query(models.FixedIp).\ + options(joinedload('instance.metadata')) + floating_ip_query = session.query(models.FloatingIp).\ + options(joinedload_all('fixed_ip.instance.metadata')) + + # Query both FixedIp and FloatingIp tables to get matches. + # Since someone could theoretically search for something that matches + # instances in both tables... we need to use a dictionary keyed + # on instance ID to make sure we return only 1. We can't key off + # of 'instance' because it's just a reference and will be different + # addresses even though they might point to the same instance ID. + instances = {} + + # MySQL 'regexp' is not portable, so we must do our own matching. + # First... grab all of the IP entries. + if is_admin_context(context): + fixed_ips = fixed_ip_query.\ + filter_by(deleted=can_read_deleted(context)).\ + all() + floating_ips = floating_ip_query.\ + filter_by(deleted=can_read_deleted(context)).\ + all() + elif is_user_context(context): + fixed_ips = fixed_ip_query.filter_by(deleted=False).all() + floating_ips = floating_ip_query.filter_by(deleted=False).all() + else: + return None + + if fixed_ips is None: + fixed_ips = [] + if floating_ips is None: + floating_ips = [] + + compiled_regexp = re.compile(ip_regexp) + instances = {} + + # Now do the regexp matching + for fixed_ip in fixed_ips: + if fixed_ip.instance and compiled_regexp.match(fixed_ip.address): + instances[fixed_ip.instance.uuid] = fixed_ip.instance + for floating_ip in floating_ips: + fixed_ip = floating_ip.fixed_ip + if fixed_ip and fixed_ip.instance and\ + compiled_regexp.match(floating_ip.address): + instances[fixed_ip.instance.uuid] = fixed_ip.instance + + return instances.values() + +@require_context +def instance_get_all_by_ipv6_regex(context, ipv6_regexp): + """Get all instances by using regular expression matching against + IPv6 Addresses + """ + + session = get_session() + with session.begin(): + # get instances + + all_instances = session.query(models.Instance).\ + options(joinedload('metadata')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + if not all_instances: + return [] + + instances = [] + compiled_regexp = re.compile(ipv6_regexp) + for instance in all_instances: + ipv6_addrs = _ipv6_get_by_instance_ref(context, instance) + for ipv6 in ipv6_addrs: + if compiled_regexp.match(ipv6): + instances.append(instance) + break + return instances @require_admin_context @@ -1258,29 +1373,33 @@ def instance_get_fixed_addresses(context, instance_id): return [fixed_ip.address for fixed_ip in fixed_ips] +def _ipv6_get_by_instance_ref(context, instance_ref): + # assume instance has 1 mac for each network associated with it + # get networks associated with instance + network_refs = network_get_all_by_instance(context, instance_id) + # compile a list of cidr_v6 prefixes sorted by network id + prefixes = [ref.cidr_v6 for ref in + sorted(network_refs, key=lambda ref: ref.id)] + # get vifs associated with instance + vif_refs = virtual_interface_get_by_instance(context, instance_ref.id) + # compile list of the mac_addresses for vifs sorted by network id + macs = [vif_ref['address'] for vif_ref in + sorted(vif_refs, key=lambda vif_ref: vif_ref['network_id'])] + # get project id from instance + project_id = instance_ref.project_id + # combine prefixes, macs, and project_id into (prefix,mac,p_id) tuples + prefix_mac_tuples = zip(prefixes, macs, [project_id for m in macs]) + # return list containing ipv6 address for each tuple + return [ipv6.to_global_ipv6(*t) for t in prefix_mac_tuples] + + @require_context def instance_get_fixed_addresses_v6(context, instance_id): session = get_session() with session.begin(): # get instance instance_ref = instance_get(context, instance_id, session=session) - # assume instance has 1 mac for each network associated with it - # get networks associated with instance - network_refs = network_get_all_by_instance(context, instance_id) - # compile a list of cidr_v6 prefixes sorted by network id - prefixes = [ref.cidr_v6 for ref in - sorted(network_refs, key=lambda ref: ref.id)] - # get vifs associated with instance - vif_refs = virtual_interface_get_by_instance(context, instance_ref.id) - # compile list of the mac_addresses for vifs sorted by network id - macs = [vif_ref['address'] for vif_ref in - sorted(vif_refs, key=lambda vif_ref: vif_ref['network_id'])] - # get project id from instance - project_id = instance_ref.project_id - # combine prefixes, macs, and project_id into (prefix,mac,p_id) tuples - prefix_mac_tuples = zip(prefixes, macs, [project_id for m in macs]) - # return list containing ipv6 address for each tuple - return [ipv6.to_global_ipv6(*t) for t in prefix_mac_tuples] + return _ipv6_get_by_instance_ref(context, instance_ref) @require_context diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d29d3d6f1..e42da193f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -187,6 +187,7 @@ class Instance(BASE, NovaBase): image_ref = Column(String(255)) kernel_id = Column(String(255)) ramdisk_id = Column(String(255)) + server_name = Column(String(255)) # image_ref = Column(Integer, ForeignKey('images.id'), nullable=True) # kernel_id = Column(Integer, ForeignKey('images.id'), nullable=True) -- cgit From 04804aba3c995260cf376b8d979f032942cd0988 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 11 Jul 2011 14:45:27 -0700 Subject: pep8 fixes --- nova/compute/api.py | 5 ++--- nova/db/sqlalchemy/api.py | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index e80e52566..fba56a2bb 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -641,7 +641,6 @@ class API(base.Base): raise exception.FixedIpNotFoundForAddress(address=fixed_ip) return instances - def _get_all_by_ip(self, context, search_opts): return self.db.instance_get_all_by_ip_regexp( context, search_opts['ip']) @@ -690,8 +689,8 @@ class API(base.Base): LOG.error(_("More than 1 mutually exclusive " "search option specified (%(found_opt)s and " "%(opt)s were both specified") % locals()) - raise exception.InvalidInput(reason= - _("More than 1 mutually exclusive " + raise exception.InvalidInput(reason=_( + "More than 1 mutually exclusive " "search option specified (%(found_opt)s and " "%(opt)s were both specified") % locals()) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index af2acbcb3..99e96e679 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1317,6 +1317,7 @@ def instance_get_all_by_ip_regexp(context, ip_regexp): return instances.values() + @require_context def instance_get_all_by_ipv6_regex(context, ipv6_regexp): """Get all instances by using regular expression matching against -- cgit From a3096d593fbe21625e3c4102e69d12950e9d2ef2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 12 Jul 2011 02:01:09 -0700 Subject: added searching by instance name added unit tests --- nova/compute/api.py | 5 + nova/db/api.py | 7 ++ nova/db/sqlalchemy/api.py | 24 +++- nova/tests/test_compute.py | 265 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index fba56a2bb..605b0d29c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -641,6 +641,10 @@ class API(base.Base): raise exception.FixedIpNotFoundForAddress(address=fixed_ip) return instances + def _get_all_by_name(self, context, search_opts): + return self.db.instance_get_all_by_name_regexp( + context, search_opts['name']) + def _get_all_by_ip(self, context, search_opts): return self.db.instance_get_all_by_ip_regexp( context, search_opts['ip']) @@ -673,6 +677,7 @@ class API(base.Base): exclusive_opts = ['reservation_id', 'project_id', 'fixed_ip', + 'name', 'ip', 'ip6'] + search_columns diff --git a/nova/db/api.py b/nova/db/api.py index c0f49d98d..6aa44f06b 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -529,6 +529,13 @@ def instance_get_all_by_column_regexp(context, column, column_regexp): column_regexp) +def instance_get_all_by_name_regexp(context, name_regexp): + """Get all instances by using regular expression matching against + its name + """ + return IMPL.instance_get_all_by_name_regexp(context, name_regexp) + + def instance_get_all_by_ip_regexp(context, ip_regexp): """Get all instances by using regular expression matching against Floating and Fixed IP Addresses diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 99e96e679..93614a307 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1262,6 +1262,28 @@ def instance_get_all_by_column_regexp(context, column, column_regexp): return instances +@require_context +def instance_get_all_by_name_regexp(context, ipv6_regexp): + """Get all instances by using regular expression matching against + its name + """ + + session = get_session() + with session.begin(): + # get instances + + all_instances = session.query(models.Instance).\ + options(joinedload('metadata')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + if not all_instances: + return [] + + compiled_regexp = re.compile(ipv6_regexp) + return [instance for instance in all_instances + if compiled_regexp.match(instance.name)] + + @require_context def instance_get_all_by_ip_regexp(context, ip_regexp): """Get all instances by using regular expression matching against @@ -1319,7 +1341,7 @@ def instance_get_all_by_ip_regexp(context, ip_regexp): @require_context -def instance_get_all_by_ipv6_regex(context, ipv6_regexp): +def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): """Get all instances by using regular expression matching against IPv6 Addresses """ diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 45cd2f764..0190a5f73 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -30,6 +30,7 @@ from nova.compute import power_state from nova import context from nova import db from nova.db.sqlalchemy import models +from nova.db.sqlalchemy import api as sqlalchemy_api from nova import exception from nova import flags import nova.image.fake @@ -810,3 +811,267 @@ class ComputeTestCase(test.TestCase): LOG.info(_("After force-killing instances: %s"), instances) self.assertEqual(len(instances), 1) self.assertEqual(power_state.SHUTOFF, instances[0]['state']) + + def test_get_all_by_display_name_regexp(self): + """Test searching instances by display_name""" + c = context.get_admin_context() + instance_id1 = self._create_instance({'display_name': 'woot'}) + instance_id2 = self._create_instance({ + 'display_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'display_name': 'not-woot', + 'id': 30}) + + instances = self.compute_api.get_all(c, + search_opts={'display_name': 'woo.*'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id2 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'display_name': 'woot.*'}) + instance_ids = [instance.id for instance in instances] + self.assertEqual(len(instances), 1) + self.assertTrue(instance_id1 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'display_name': '.*oot.*'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'display_name': 'n.*'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id3 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'display_name': 'noth.*'}) + self.assertEqual(len(instances), 0) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_all_by_server_name_regexp(self): + """Test searching instances by server_name""" + c = context.get_admin_context() + instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id2 = self._create_instance({ + 'server_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'server_name': 'not-woot', + 'id': 30}) + + instances = self.compute_api.get_all(c, + search_opts={'server_name': 'woo.*'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id2 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'server_name': 'woot.*'}) + instance_ids = [instance.id for instance in instances] + self.assertEqual(len(instances), 1) + self.assertTrue(instance_id1 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'server_name': '.*oot.*'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'server_name': 'n.*'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id3 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'server_name': 'noth.*'}) + self.assertEqual(len(instances), 0) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_all_by_name_regexp(self): + """Test searching instances by name""" + self.flags(instance_name_template='instance-%d') + + c = context.get_admin_context() + instance_id1 = self._create_instance() + instance_id2 = self._create_instance({'id': 2}) + instance_id3 = self._create_instance({'id': 10}) + + instances = self.compute_api.get_all(c, + search_opts={'name': 'instance.*'}) + self.assertEqual(len(instances), 3) + + instances = self.compute_api.get_all(c, + search_opts={'name': '.*\-\d$'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id2 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'name': 'i.*2'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id2) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_by_fixed_ip(self): + """Test getting 1 instance by Fixed IP""" + c = context.get_admin_context() + instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id2 = self._create_instance({ + 'server_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'server_name': 'not-woot', + 'id': 30}) + + db.fixed_ip_create(c, + {'address': '1.1.1.1', + 'instance_id': instance_id1}) + db.fixed_ip_create(c, + {'address': '1.1.2.1', + 'instance_id': instance_id2}) + + # regex not allowed + self.assertRaises(exception.NotFound, + self.compute_api.get_all, + c, + search_opts={'fixed_ip': '.*'}) + + self.assertRaises(exception.NotFound, + self.compute_api.get_all, + c, + search_opts={'fixed_ip': '1.1.3.1'}) + + instances = self.compute_api.get_all(c, + search_opts={'fixed_ip': '1.1.1.1'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id1) + + instances = self.compute_api.get_all(c, + search_opts={'fixed_ip': '1.1.2.1'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id2) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + + def test_get_all_by_ip_regex(self): + """Test searching by Floating and Fixed IP""" + c = context.get_admin_context() + instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id2 = self._create_instance({ + 'server_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'server_name': 'not-woot', + 'id': 30}) + + db.fixed_ip_create(c, + {'address': '1.1.1.1', + 'instance_id': instance_id1}) + db.fixed_ip_create(c, + {'address': '1.1.2.1', + 'instance_id': instance_id2}) + fix_addr = db.fixed_ip_create(c, + {'address': '1.1.3.1', + 'instance_id': instance_id3}) + fix_ref = db.fixed_ip_get_by_address(c, fix_addr) + flo_ref = db.floating_ip_create(c, + {'address': '10.0.0.2', + 'fixed_ip_id': fix_ref['id']}) + + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*\.1'}) + self.assertEqual(len(instances), 3) + + instances = self.compute_api.get_all(c, + search_opts={'ip': '1.*'}) + self.assertEqual(len(instances), 3) + + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*\.1.\d+$'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*\.2.+'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id2 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'ip': '10.*'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id3 in instance_ids) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + db.floating_ip_destroy(c, '10.0.0.2') + + def test_get_all_by_ipv6_regex(self): + """Test searching by IPv6 address""" + def fake_ipv6_get_by_instance_ref(context, instance): + if instance.id == 1: + return ['ffff:ffff::1'] + if instance.id == 20: + return ['dddd:dddd::1'] + if instance.id == 30: + return ['cccc:cccc::1', 'eeee:eeee::1', 'dddd:dddd::1'] + + self.stubs.Set(sqlalchemy_api, '_ipv6_get_by_instance_ref', + fake_ipv6_get_by_instance_ref) + + c = context.get_admin_context() + instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id2 = self._create_instance({ + 'server_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'server_name': 'not-woot', + 'id': 30}) + + instances = self.compute_api.get_all(c, + search_opts={'ip6': 'ff.*'}) + self.assertEqual(len(instances), 1) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'ip6': '.*::1'}) + self.assertEqual(len(instances), 3) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id1 in instance_ids) + self.assertTrue(instance_id2 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + instances = self.compute_api.get_all(c, + search_opts={'ip6': '.*dd:.*'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id2 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) -- cgit From edccef06c24df2fa785005f7a3c1f52a45bfc071 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 12 Jul 2011 03:13:43 -0700 Subject: fix bugs with fixed_ip returning a 404 instance searching needs to joinload more stuff --- nova/compute/api.py | 17 +++++++- nova/db/sqlalchemy/api.py | 102 +++++++++++++++++++++++----------------------- 2 files changed, 68 insertions(+), 51 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 605b0d29c..511c17e7a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -19,6 +19,7 @@ """Handles all requests relating to instances (guest vms).""" import eventlet +import novaclient import re import time @@ -636,7 +637,10 @@ class API(base.Base): try: instances = self.db.instance_get_by_fixed_ip(context, fixed_ip) except exception.FixedIpNotFound, e: - raise + if search_opts['recurse_zones']: + return [] + else: + raise if not instances: raise exception.FixedIpNotFoundForAddress(address=fixed_ip) return instances @@ -729,14 +733,25 @@ class API(base.Base): admin_context = context.elevated() children = scheduler_api.call_zone_method(admin_context, "list", + errors_to_ignore=[novaclient.exceptions.NotFound], novaclient_collection_name="servers", search_opts=search_opts) for zone, servers in children: + # 'servers' can be None if a 404 was returned by a zone + if servers is None: + continue for server in servers: # Results are ready to send to user. No need to scrub. server._info['_is_precooked'] = True instances.append(server._info) + + # Fixed IP returns a FixedIpNotFound when an instance is not + # found... + fixed_ip = search_opts.get('fixed_ip', None) + if fixed_ip and not instances: + raise exception.FixedIpNotFoundForAddress(address=fixed_ip) + return instances def _cast_compute_message(self, method, context, instance_id, host=None, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 93614a307..59db56a5c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1234,27 +1234,20 @@ def instance_get_all_by_column_regexp(context, column, column_regexp): # MySQL 'regexp' is not portable, so we must do our own matching. # First... grab all Instances. - query = session.query(models.Instance).\ - options(joinedload('metadata')) - if is_admin_context(context): - all_instances = query.\ - filter_by(deleted=can_read_deleted(context)).\ - all() - elif is_user_context(context): - all_instances = query.\ - filter_by(project_id=context.project_id).\ - filter_by(deleted=False).\ - all() - else: + all_instances = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + if not all_instances: return [] - - if all_instances is None: - all_instances = [] - # Now do the regexp matching compiled_regexp = re.compile(column_regexp) instances = [] - for instance in all_instances: v = getattr(instance, column) if v and compiled_regexp.match(v): @@ -1269,19 +1262,24 @@ def instance_get_all_by_name_regexp(context, ipv6_regexp): """ session = get_session() - with session.begin(): - # get instances - - all_instances = session.query(models.Instance).\ - options(joinedload('metadata')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() - if not all_instances: - return [] - compiled_regexp = re.compile(ipv6_regexp) - return [instance for instance in all_instances - if compiled_regexp.match(instance.name)] + # MySQL 'regexp' is not portable, so we must do our own matching. + # First... grab all Instances. + all_instances = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + if not all_instances: + return [] + # Now do the regexp matching + compiled_regexp = re.compile(ipv6_regexp) + return [instance for instance in all_instances + if compiled_regexp.match(instance.name)] @require_context @@ -1291,11 +1289,6 @@ def instance_get_all_by_ip_regexp(context, ip_regexp): """ session = get_session() - fixed_ip_query = session.query(models.FixedIp).\ - options(joinedload('instance.metadata')) - floating_ip_query = session.query(models.FloatingIp).\ - options(joinedload_all('fixed_ip.instance.metadata')) - # Query both FixedIp and FloatingIp tables to get matches. # Since someone could theoretically search for something that matches # instances in both tables... we need to use a dictionary keyed @@ -1304,20 +1297,26 @@ def instance_get_all_by_ip_regexp(context, ip_regexp): # addresses even though they might point to the same instance ID. instances = {} - # MySQL 'regexp' is not portable, so we must do our own matching. - # First... grab all of the IP entries. - if is_admin_context(context): - fixed_ips = fixed_ip_query.\ - filter_by(deleted=can_read_deleted(context)).\ - all() - floating_ips = floating_ip_query.\ - filter_by(deleted=can_read_deleted(context)).\ - all() - elif is_user_context(context): - fixed_ips = fixed_ip_query.filter_by(deleted=False).all() - floating_ips = floating_ip_query.filter_by(deleted=False).all() - else: - return None + fixed_ips = session.query(models.FixedIp).\ + options(joinedload_all('instance.fixed_ips.floating_ips')).\ + options(joinedload('instance.virtual_interfaces')).\ + options(joinedload('instance.security_groups')).\ + options(joinedload_all('instance.fixed_ips.network')).\ + options(joinedload('instance.metadata')).\ + options(joinedload('instance.instance_type')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + floating_ips = session.query(models.FloatingIp).\ + options(joinedload_all( + 'fixed_ip.instance.fixed_ips.floating_ips')).\ + options(joinedload('fixed_ip.instance.virtual_interfaces')).\ + options(joinedload('fixed_ip.instance.security_groups')).\ + options(joinedload_all( + 'fixed_ip.instance.fixed_ips.network')).\ + options(joinedload('fixed_ip.instance.metadata')).\ + options(joinedload('fixed_ip.instance.instance_type')).\ + filter_by(deleted=can_read_deleted(context)).\ + all() if fixed_ips is None: fixed_ips = [] @@ -1348,10 +1347,13 @@ def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): session = get_session() with session.begin(): - # get instances - all_instances = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ all() if not all_instances: -- cgit From 1dec3d7c3380d83398be0588b58c1cad13252807 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 14 Jul 2011 12:13:13 -0700 Subject: clean up checking for exclusive search options fix a cut n paste error with instance_get_all_by_name_regexp --- nova/compute/api.py | 32 +++++++++++++++----------------- nova/db/sqlalchemy/api.py | 4 ++-- 2 files changed, 17 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index f795e345a..a445ef6eb 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -689,21 +689,19 @@ class API(base.Base): # Ignore unknown search options for possible forward compatability. # Raise an exception if more than 1 search option is specified found_opt = None - for opt in exclusive_opts: - v = search_opts.get(opt, None) - if v: - if found_opt is None: - found_opt = opt - else: - LOG.error(_("More than 1 mutually exclusive " - "search option specified (%(found_opt)s and " - "%(opt)s were both specified") % locals()) - raise exception.InvalidInput(reason=_( - "More than 1 mutually exclusive " - "search option specified (%(found_opt)s and " - "%(opt)s were both specified") % locals()) - - if found_opt: + found_opts = [opt for opt in exclusive_opts + if search_opts.get(opt, None)] + if len(found_opts) > 1: + found_opt_str = ", ".join(found_opts) + msg = _("More than 1 mutually exclusive " + "search option specified: %(found_opt_str)s") \ + % locals() + logger.error(msg) + raise exception.InvalidInput(reason=msg) + + # Found a search option? + if found_opts: + found_opt = found_opts[0] if found_opt in search_columns: instances = self._get_all_by_column(context, found_opt, search_opts) @@ -714,10 +712,10 @@ class API(base.Base): elif not context.is_admin: if context.project: instances = self.db.instance_get_all_by_project( - context, context.project_id) + context, context.project_id) else: instances = self.db.instance_get_all_by_user( - context, context.user_id) + context, context.user_id) else: instances = self.db.instance_get_all(context) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 59db56a5c..735d63be9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1256,7 +1256,7 @@ def instance_get_all_by_column_regexp(context, column, column_regexp): @require_context -def instance_get_all_by_name_regexp(context, ipv6_regexp): +def instance_get_all_by_name_regexp(context, name_regexp): """Get all instances by using regular expression matching against its name """ @@ -1277,7 +1277,7 @@ def instance_get_all_by_name_regexp(context, ipv6_regexp): if not all_instances: return [] # Now do the regexp matching - compiled_regexp = re.compile(ipv6_regexp) + compiled_regexp = re.compile(name_regexp) return [instance for instance in all_instances if compiled_regexp.match(instance.name)] -- 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 +++++++++++++++++++++++++++++---- nova/compute/api.py | 154 ++++++++++++++++++++++++------------------ nova/tests/test_compute.py | 8 +-- 4 files changed, 215 insertions(+), 93 deletions(-) (limited to 'nova') 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'] diff --git a/nova/compute/api.py b/nova/compute/api.py index a445ef6eb..0c76f4ad6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -623,44 +623,6 @@ class API(base.Base): """ return self.get(context, instance_id) - def _get_all_by_reservation_id(self, context, search_opts): - search_opts['recurse_zones'] = True - return self.db.instance_get_all_by_reservation( - context, search_opts['reservation_id']) - - def _get_all_by_project_id(self, context, search_opts): - return self.db.instance_get_all_by_project( - context, search_opts['project_id']) - - def _get_all_by_fixed_ip(self, context, search_opts): - fixed_ip = search_opts['fixed_ip'] - try: - instances = self.db.instance_get_by_fixed_ip(context, fixed_ip) - except exception.FixedIpNotFound, e: - if search_opts['recurse_zones']: - return [] - else: - raise - if not instances: - raise exception.FixedIpNotFoundForAddress(address=fixed_ip) - return instances - - def _get_all_by_name(self, context, search_opts): - return self.db.instance_get_all_by_name_regexp( - context, search_opts['name']) - - def _get_all_by_ip(self, context, search_opts): - return self.db.instance_get_all_by_ip_regexp( - context, search_opts['ip']) - - def _get_all_by_ip6(self, context, search_opts): - return self.db.instance_get_all_by_ipv6_regexp( - context, search_opts['ip6']) - - def _get_all_by_column(self, context, column, search_opts): - return self.db.instance_get_all_by_column_regexp( - context, column, search_opts[column]) - def get_all(self, context, search_opts=None): """Get all instances filtered by one of the given parameters. @@ -668,57 +630,115 @@ class API(base.Base): all instances in the system. """ + def _get_all_by_reservation_id(reservation_id): + """Get instances by reservation ID""" + # reservation_id implies recurse_zones + search_opts['recurse_zones'] = True + return self.db.instance_get_all_by_reservation(context, + reservation_id) + + def _get_all_by_project_id(project_id): + """Get instances by project ID""" + return self.db.instance_get_all_by_project(context, project_id) + + def _get_all_by_fixed_ip(fixed_ip): + """Get instance by fixed IP""" + try: + instances = self.db.instance_get_by_fixed_ip(context, + fixed_ip) + except exception.FixedIpNotFound, e: + if search_opts.get('recurse_zones', False): + return [] + else: + raise + if not instances: + raise exception.FixedIpNotFoundForAddress(address=fixed_ip) + return instances + + def _get_all_by_instance_name(instance_name_regexp): + """Get instances by matching the Instance.name property""" + return self.db.instance_get_all_by_name_regexp( + context, instance_name_regexp) + + def _get_all_by_ip(ip_regexp): + """Get instances by matching IPv4 addresses""" + return self.db.instance_get_all_by_ip_regexp(context, ip_regexp) + + def _get_all_by_ipv6(ipv6_regexp): + """Get instances by matching IPv6 addresses""" + return self.db.instance_get_all_by_ipv6_regexp(context, + ipv6_regexp) + + def _get_all_by_column(column_regexp, column): + """Get instances by matching Instance.""" + return self.db.instance_get_all_by_column_regexp( + context, column, column_regexp) + + # Define the search params that we will allow. This is a mapping + # of the search param to tuple of (function_to_call, (function_args)) + # A 'None' function means it's an optional parameter that will + # influence the search results, but itself is not a search option. + # Search options are mutually exclusive + known_params = { + 'recurse_zones': (None, None), + # v1.0 API? + 'fresh': (None, None), + # v1.1 API + 'changes-since': (None, None), + # Mutually exclusive options + 'display_name': (_get_all_by_column, ('display_name',)), + 'reservation_id': (_get_all_by_reservation_id, ()), + # Needed for EC2 API + 'fixed_ip': (_get_all_by_fixed_ip, ()), + # Needed for EC2 API + 'project_id': (_get_all_by_project_id, ()), + 'ip': (_get_all_by_ip, ()), + 'ip6': (_get_all_by_ipv6, ()), + 'instance_name': (_get_all_by_instance_name, ()), + 'server_name': (_get_all_by_column, ('server_name',))} + + # FIXME(comstud): 'fresh' and 'changes-since' are currently not + # implemented... + if search_opts is None: search_opts = {} LOG.debug(_("Searching by: %s") % str(search_opts)) - # Columns we can do a generic search on - search_columns = ['display_name', - 'server_name'] - - # Options that are mutually exclusive - exclusive_opts = ['reservation_id', - 'project_id', - 'fixed_ip', - 'name', - 'ip', - 'ip6'] + search_columns - - # See if a valid search option was passed in. - # Ignore unknown search options for possible forward compatability. - # Raise an exception if more than 1 search option is specified - found_opt = None - found_opts = [opt for opt in exclusive_opts - if search_opts.get(opt, None)] + # Mutually exclusive serach options are any options that have + # a function to call. Raise an exception if more than 1 is + # specified... + # NOTE(comstud): Ignore unknown options. The OS API will + # do it's own verification on options.. + found_opts = [opt for opt in search_opts.iterkeys() + if opt in known_params and \ + known_params[opt][0] is not None] if len(found_opts) > 1: found_opt_str = ", ".join(found_opts) msg = _("More than 1 mutually exclusive " "search option specified: %(found_opt_str)s") \ % locals() - logger.error(msg) + LOG.error(msg) raise exception.InvalidInput(reason=msg) # Found a search option? if found_opts: found_opt = found_opts[0] - if found_opt in search_columns: - instances = self._get_all_by_column(context, - found_opt, search_opts) - else: - method_name = '_get_all_by_%s' % found_opt - method = getattr(self, method_name, None) - instances = method(context, search_opts) - elif not context.is_admin: + f, f_args = known_params[found_opt] + instances = f(search_opts[found_opt], *f_args) + # Nope. Return all instances if the request is in admin context.. + elif context.is_admin: + instances = self.db.instance_get_all(context) + # Nope. Return all instances for the user/project + else: if context.project: instances = self.db.instance_get_all_by_project( context, context.project_id) else: instances = self.db.instance_get_all_by_user( context, context.user_id) - else: - instances = self.db.instance_get_all(context) + # Convert any responses into a list of instances if instances is None: instances = [] elif not isinstance(instances, list): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bdf2edd50..fc075b6c7 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -910,7 +910,7 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) - def test_get_all_by_name_regexp(self): + def test_get_all_by_instance_name_regexp(self): """Test searching instances by name""" self.flags(instance_name_template='instance-%d') @@ -920,18 +920,18 @@ class ComputeTestCase(test.TestCase): instance_id3 = self._create_instance({'id': 10}) instances = self.compute_api.get_all(c, - search_opts={'name': 'instance.*'}) + search_opts={'instance_name': 'instance.*'}) self.assertEqual(len(instances), 3) instances = self.compute_api.get_all(c, - search_opts={'name': '.*\-\d$'}) + search_opts={'instance_name': '.*\-\d$'}) self.assertEqual(len(instances), 2) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id1 in instance_ids) self.assertTrue(instance_id2 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'name': 'i.*2'}) + search_opts={'instance_name': 'i.*2'}) self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id2) -- cgit From 491c90924ac87e533ce61e3bf949a50bfdd6a31d Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Sun, 17 Jul 2011 16:35:11 -0700 Subject: compute's get_all should accept 'name' not 'display_name' for searching Instance.display_name. Removed 'server_name' searching.. Fixed DB calls for searching to filter results based on context --- nova/compute/api.py | 5 ++-- nova/db/sqlalchemy/api.py | 58 ++++++++++++++++++++++++++++++++------ nova/tests/test_compute.py | 69 +++++++--------------------------------------- 3 files changed, 61 insertions(+), 71 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 0c76f4ad6..6661775a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -686,7 +686,7 @@ class API(base.Base): # v1.1 API 'changes-since': (None, None), # Mutually exclusive options - 'display_name': (_get_all_by_column, ('display_name',)), + 'name': (_get_all_by_column, ('display_name',)), 'reservation_id': (_get_all_by_reservation_id, ()), # Needed for EC2 API 'fixed_ip': (_get_all_by_fixed_ip, ()), @@ -694,8 +694,7 @@ class API(base.Base): 'project_id': (_get_all_by_project_id, ()), 'ip': (_get_all_by_ip, ()), 'ip6': (_get_all_by_ipv6, ()), - 'instance_name': (_get_all_by_instance_name, ()), - 'server_name': (_get_all_by_column, ('server_name',))} + 'instance_name': (_get_all_by_instance_name, ())} # FIXME(comstud): 'fresh' and 'changes-since' are currently not # implemented... diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 735d63be9..feccba389 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1234,15 +1234,25 @@ def instance_get_all_by_column_regexp(context, column, column_regexp): # MySQL 'regexp' is not portable, so we must do our own matching. # First... grab all Instances. - all_instances = session.query(models.Instance).\ + prefix = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() + filter_by(deleted=can_read_deleted(context)) + + if context.is_admin: + all_instances = prefix.all() + elif context.project: + all_instances = prefix.\ + filter_by(project_id=context.project_id).\ + all() + else: + all_instances = prefix.\ + filter_by(user_id=context.user_id).\ + all() if not all_instances: return [] # Now do the regexp matching @@ -1265,15 +1275,25 @@ def instance_get_all_by_name_regexp(context, name_regexp): # MySQL 'regexp' is not portable, so we must do our own matching. # First... grab all Instances. - all_instances = session.query(models.Instance).\ + prefix = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() + filter_by(deleted=can_read_deleted(context)) + + if context.is_admin: + all_instances = prefix.all() + elif context.project: + all_instances = prefix.\ + filter_by(project_id=context.project_id).\ + all() + else: + all_instances = prefix.\ + filter_by(user_id=context.user_id).\ + all() if not all_instances: return [] # Now do the regexp matching @@ -1336,6 +1356,15 @@ def instance_get_all_by_ip_regexp(context, ip_regexp): compiled_regexp.match(floating_ip.address): instances[fixed_ip.instance.uuid] = fixed_ip.instance + if context.is_admin: + return instances.values() + elif context.project: + return [instance for instance in instances.values() + if instance.project_id == context.project_id] + else: + return [instance for instance in instances.values() + if instance.user_id == context.user_id] + return instances.values() @@ -1347,15 +1376,26 @@ def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): session = get_session() with session.begin(): - all_instances = session.query(models.Instance).\ + prefix = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload('virtual_interfaces')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() + filter_by(deleted=can_read_deleted(context)) + + if context.is_admin: + all_instances = prefix.all() + elif context.project: + all_instances = prefix.\ + filter_by(project_id=context.project_id).\ + all() + else: + all_instances = prefix.\ + filter_by(user_id=context.user_id).\ + all() + if not all_instances: return [] diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index fc075b6c7..152687083 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -820,8 +820,8 @@ class ComputeTestCase(test.TestCase): self.assertEqual(len(instances), 1) self.assertEqual(power_state.SHUTOFF, instances[0]['state']) - def test_get_all_by_display_name_regexp(self): - """Test searching instances by display_name""" + def test_get_all_by_name_regexp(self): + """Test searching instances by name (display_name)""" c = context.get_admin_context() instance_id1 = self._create_instance({'display_name': 'woot'}) instance_id2 = self._create_instance({ @@ -832,78 +832,33 @@ class ComputeTestCase(test.TestCase): 'id': 30}) instances = self.compute_api.get_all(c, - search_opts={'display_name': 'woo.*'}) + search_opts={'name': 'woo.*'}) self.assertEqual(len(instances), 2) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id1 in instance_ids) self.assertTrue(instance_id2 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'display_name': 'woot.*'}) + search_opts={'name': 'woot.*'}) instance_ids = [instance.id for instance in instances] self.assertEqual(len(instances), 1) self.assertTrue(instance_id1 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'display_name': '.*oot.*'}) + search_opts={'name': '.*oot.*'}) self.assertEqual(len(instances), 2) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id1 in instance_ids) self.assertTrue(instance_id3 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'display_name': 'n.*'}) + search_opts={'name': 'n.*'}) self.assertEqual(len(instances), 1) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id3 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'display_name': 'noth.*'}) - self.assertEqual(len(instances), 0) - - db.instance_destroy(c, instance_id1) - db.instance_destroy(c, instance_id2) - db.instance_destroy(c, instance_id3) - - def test_get_all_by_server_name_regexp(self): - """Test searching instances by server_name""" - c = context.get_admin_context() - instance_id1 = self._create_instance({'server_name': 'woot'}) - instance_id2 = self._create_instance({ - 'server_name': 'woo', - 'id': 20}) - instance_id3 = self._create_instance({ - 'server_name': 'not-woot', - 'id': 30}) - - instances = self.compute_api.get_all(c, - search_opts={'server_name': 'woo.*'}) - self.assertEqual(len(instances), 2) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id1 in instance_ids) - self.assertTrue(instance_id2 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'server_name': 'woot.*'}) - instance_ids = [instance.id for instance in instances] - self.assertEqual(len(instances), 1) - self.assertTrue(instance_id1 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'server_name': '.*oot.*'}) - self.assertEqual(len(instances), 2) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id1 in instance_ids) - self.assertTrue(instance_id3 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'server_name': 'n.*'}) - self.assertEqual(len(instances), 1) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id3 in instance_ids) - - instances = self.compute_api.get_all(c, - search_opts={'server_name': 'noth.*'}) + search_opts={'name': 'noth.*'}) self.assertEqual(len(instances), 0) db.instance_destroy(c, instance_id1) @@ -942,13 +897,9 @@ class ComputeTestCase(test.TestCase): def test_get_by_fixed_ip(self): """Test getting 1 instance by Fixed IP""" c = context.get_admin_context() - instance_id1 = self._create_instance({'server_name': 'woot'}) - instance_id2 = self._create_instance({ - 'server_name': 'woo', - 'id': 20}) - instance_id3 = self._create_instance({ - 'server_name': 'not-woot', - 'id': 30}) + instance_id1 = self._create_instance() + instance_id2 = self._create_instance({'id': 20}) + instance_id3 = self._create_instance({'id': 30}) db.fixed_ip_create(c, {'address': '1.1.1.1', -- 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 +--- nova/compute/api.py | 47 +++++++++-- nova/compute/power_state.py | 29 +++++++ nova/db/api.py | 5 ++ nova/db/sqlalchemy/api.py | 42 ++++++++++ nova/tests/test_compute.py | 156 ++++++++++++++++++++++++++++++++---- 7 files changed, 271 insertions(+), 41 deletions(-) (limited to 'nova') 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() diff --git a/nova/compute/api.py b/nova/compute/api.py index 6661775a5..cd2aaed96 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -669,11 +669,32 @@ class API(base.Base): return self.db.instance_get_all_by_ipv6_regexp(context, ipv6_regexp) - def _get_all_by_column(column_regexp, column): - """Get instances by matching Instance.""" + def _get_all_by_column_regexp(column_regexp, column): + """Get instances by regular expression matching + Instance. + """ return self.db.instance_get_all_by_column_regexp( context, column, column_regexp) + def _get_all_by_column(column_data, column): + """Get instances by regular expression matching + Instance. + """ + return self.db.instance_get_all_by_column( + context, column, column_data) + + def _get_all_by_flavor(flavor_id): + """Get instances by regular expression matching + Instance. + """ + try: + instance_type = self.db.instance_type_get_by_flavor_id( + context, flavor_id) + except exception.FlavorNotFound: + return [] + return self.db.instance_get_all_by_column( + context, 'instance_type_id', instance_type['id']) + # Define the search params that we will allow. This is a mapping # of the search param to tuple of (function_to_call, (function_args)) # A 'None' function means it's an optional parameter that will @@ -686,7 +707,7 @@ class API(base.Base): # v1.1 API 'changes-since': (None, None), # Mutually exclusive options - 'name': (_get_all_by_column, ('display_name',)), + 'name': (_get_all_by_column_regexp, ('display_name',)), 'reservation_id': (_get_all_by_reservation_id, ()), # Needed for EC2 API 'fixed_ip': (_get_all_by_fixed_ip, ()), @@ -694,7 +715,10 @@ class API(base.Base): 'project_id': (_get_all_by_project_id, ()), 'ip': (_get_all_by_ip, ()), 'ip6': (_get_all_by_ipv6, ()), - 'instance_name': (_get_all_by_instance_name, ())} + 'instance_name': (_get_all_by_instance_name, ()), + 'image': (_get_all_by_column, ('image_ref',)), + 'state': (_get_all_by_column, ('state',)), + 'flavor': (_get_all_by_flavor, ())} # FIXME(comstud): 'fresh' and 'changes-since' are currently not # implemented... @@ -746,13 +770,26 @@ class API(base.Base): if not search_opts.get('recurse_zones', False): return instances + new_search_opts = {} + new_search_opts.update(search_opts) + # API does state search by status, instead of the real power + # state. So if we're searching by 'state', we need to + # convert this back into 'status' + state = new_search_opts.pop('state', None) + if state: + # Might be a list.. we can only use 1. + if isinstance(state, list): + state = state[0] + new_search_opts['status'] = power_state.status_from_state( + state) + # Recurse zones. Need admin context for this. admin_context = context.elevated() children = scheduler_api.call_zone_method(admin_context, "list", errors_to_ignore=[novaclient.exceptions.NotFound], novaclient_collection_name="servers", - search_opts=search_opts) + search_opts=new_search_opts) for zone, servers in children: # 'servers' can be None if a 404 was returned by a zone diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index c468fe6b3..834ad1c0a 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -55,3 +55,32 @@ def name(code): def valid_states(): return _STATE_MAP.keys() + +_STATUS_MAP = { + None: 'BUILD', + NOSTATE: 'BUILD', + RUNNING: 'ACTIVE', + BLOCKED: 'ACTIVE', + SUSPENDED: 'SUSPENDED', + PAUSED: 'PAUSED', + SHUTDOWN: 'SHUTDOWN', + SHUTOFF: 'SHUTOFF', + CRASHED: 'ERROR', + FAILED: 'ERROR', + BUILDING: 'BUILD', +} + +def status_from_state(power_state): + """Map the power state to the server status string""" + return _STATUS_MAP[power_state] + +def 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 not None: + continue + if status.lower() == status_map.lower(): + power_states.append(power_state) + return power_states diff --git a/nova/db/api.py b/nova/db/api.py index 6aa44f06b..b1b9b4544 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -520,6 +520,11 @@ def instance_get_by_fixed_ipv6(context, address): return IMPL.instance_get_by_fixed_ipv6(context, address) +def instance_get_all_by_column(context, column, column_data): + """Get all instances by exact match against the specified DB column""" + return IMPL.instance_get_all_by_column(context, column, column_data) + + def instance_get_all_by_column_regexp(context, column, column_regexp): """Get all instances by using regular expression matching against a particular DB column diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index feccba389..5209e9c8d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1225,6 +1225,48 @@ def instance_get_by_fixed_ipv6(context, address): return result +@require_context +def instance_get_all_by_column(context, column, column_data): + """Get all instances by exact match against the specified DB column + 'column_data' can be a list. + """ + session = get_session() + + + prefix = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('virtual_interfaces')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)) + + if isinstance(column_data, list): + column_attr = getattr(models.Instance, column) + prefix = prefix.filter(column_attr.in_(column_data)) + else: + # Set up the dictionary for filter_by() + query_filter = {} + query_filter[column] = column_data + prefix = prefix.filter_by(**query_filter) + + if context.is_admin: + all_instances = prefix.all() + elif context.project: + all_instances = prefix.\ + filter_by(project_id=context.project_id).\ + all() + else: + all_instances = prefix.\ + filter_by(user_id=context.user_id).\ + all() + if not all_instances: + return [] + + return all_instances + + @require_context def instance_get_all_by_column_regexp(context, column, column_regexp): """Get all instances by using regular expression matching against diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 152687083..bab58a7b1 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -84,8 +84,11 @@ class ComputeTestCase(test.TestCase): self.manager.delete_project(self.project) super(ComputeTestCase, self).tearDown() - def _create_instance(self, params={}): + def _create_instance(self, params=None): """Create a test instance""" + + if params is None: + params = {} inst = {} inst['image_ref'] = 1 inst['reservation_id'] = 'r-fakeres' @@ -825,11 +828,11 @@ class ComputeTestCase(test.TestCase): c = context.get_admin_context() instance_id1 = self._create_instance({'display_name': 'woot'}) instance_id2 = self._create_instance({ - 'display_name': 'woo', - 'id': 20}) + 'display_name': 'woo', + 'id': 20}) instance_id3 = self._create_instance({ - 'display_name': 'not-woot', - 'id': 30}) + 'display_name': 'not-woot', + 'id': 30}) instances = self.compute_api.get_all(c, search_opts={'name': 'woo.*'}) @@ -937,11 +940,11 @@ class ComputeTestCase(test.TestCase): c = context.get_admin_context() instance_id1 = self._create_instance({'server_name': 'woot'}) instance_id2 = self._create_instance({ - 'server_name': 'woo', - 'id': 20}) + 'server_name': 'woo', + 'id': 20}) instance_id3 = self._create_instance({ - 'server_name': 'not-woot', - 'id': 30}) + 'server_name': 'not-woot', + 'id': 30}) db.fixed_ip_create(c, {'address': '1.1.1.1', @@ -974,14 +977,12 @@ class ComputeTestCase(test.TestCase): instances = self.compute_api.get_all(c, search_opts={'ip': '.*\.2.+'}) self.assertEqual(len(instances), 1) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id2 in instance_ids) + self.assertEqual(instances[0].id, instance_id2) instances = self.compute_api.get_all(c, search_opts={'ip': '10.*'}) self.assertEqual(len(instances), 1) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id3 in instance_ids) + self.assertEqual(instances[0].id, instance_id3) db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) @@ -1004,15 +1005,16 @@ class ComputeTestCase(test.TestCase): c = context.get_admin_context() instance_id1 = self._create_instance({'server_name': 'woot'}) instance_id2 = self._create_instance({ - 'server_name': 'woo', - 'id': 20}) + 'server_name': 'woo', + 'id': 20}) instance_id3 = self._create_instance({ - 'server_name': 'not-woot', - 'id': 30}) + 'server_name': 'not-woot', + 'id': 30}) instances = self.compute_api.get_all(c, search_opts={'ip6': 'ff.*'}) self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id1) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id1 in instance_ids) @@ -1034,3 +1036,123 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) + + def test_get_all_by_image(self): + """Test searching instances by image""" + + c = context.get_admin_context() + instance_id1 = self._create_instance({'image_ref': '1234'}) + instance_id2 = self._create_instance({ + 'id': 2, + 'image_ref': '4567'}) + instance_id3 = self._create_instance({ + 'id': 10, + 'image_ref': '4567'}) + + instances = self.compute_api.get_all(c, + search_opts={'image': '123'}) + self.assertEqual(len(instances), 0) + + instances = self.compute_api.get_all(c, + search_opts={'image': '1234'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id1) + + instances = self.compute_api.get_all(c, + search_opts={'image': '4567'}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id2 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + # Test passing a list as search arg + instances = self.compute_api.get_all(c, + search_opts={'image': ['1234', '4567']}) + self.assertEqual(len(instances), 3) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_all_by_flavor(self): + """Test searching instances by image""" + + c = context.get_admin_context() + instance_id1 = self._create_instance({'instance_type_id': 1}) + instance_id2 = self._create_instance({ + 'id': 2, + 'instance_type_id': 2}) + instance_id3 = self._create_instance({ + 'id': 10, + 'instance_type_id': 2}) + + # NOTE(comstud): Migrations set up the instance_types table + # for us. Therefore, we assume the following is true for + # these tests: + # instance_type_id 1 == flavor 3 + # instance_type_id 2 == flavor 1 + # instance_type_id 3 == flavor 4 + # instance_type_id 4 == flavor 5 + # instance_type_id 5 == flavor 2 + + instances = self.compute_api.get_all(c, + search_opts={'flavor': 5}) + self.assertEqual(len(instances), 0) + + instances = self.compute_api.get_all(c, + search_opts={'flavor': 99}) + self.assertEqual(len(instances), 0) + + instances = self.compute_api.get_all(c, + search_opts={'flavor': 3}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id1) + + instances = self.compute_api.get_all(c, + search_opts={'flavor': 1}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id2 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_all_by_state(self): + """Test searching instances by state""" + + c = context.get_admin_context() + instance_id1 = self._create_instance({'state': power_state.SHUTDOWN}) + instance_id2 = self._create_instance({ + 'id': 2, + 'state': power_state.RUNNING}) + instance_id3 = self._create_instance({ + 'id': 10, + 'state': power_state.RUNNING}) + + instances = self.compute_api.get_all(c, + search_opts={'state': power_state.SUSPENDED}) + self.assertEqual(len(instances), 0) + + instances = self.compute_api.get_all(c, + search_opts={'state': power_state.SHUTDOWN}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id1) + + instances = self.compute_api.get_all(c, + search_opts={'state': power_state.RUNNING}) + self.assertEqual(len(instances), 2) + instance_ids = [instance.id for instance in instances] + self.assertTrue(instance_id2 in instance_ids) + self.assertTrue(instance_id3 in instance_ids) + + # Test passing a list as search arg + instances = self.compute_api.get_all(c, + search_opts={'state': [power_state.SHUTDOWN, + power_state.RUNNING]}) + self.assertEqual(len(instances), 3) + + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) -- 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 ++- nova/compute/api.py | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'nova') 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'] diff --git a/nova/compute/api.py b/nova/compute/api.py index cd2aaed96..c4d6f8b8b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -677,16 +677,12 @@ class API(base.Base): context, column, column_regexp) def _get_all_by_column(column_data, column): - """Get instances by regular expression matching - Instance. - """ + """Get instances by exact matching Instance.""" return self.db.instance_get_all_by_column( context, column, column_data) def _get_all_by_flavor(flavor_id): - """Get instances by regular expression matching - Instance. - """ + """Get instances by flavor ID""" try: instance_type = self.db.instance_type_get_by_flavor_id( context, flavor_id) -- 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') 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') 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') 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 +++++--- nova/compute/power_state.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') 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 diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index 834ad1c0a..8018c5270 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -79,7 +79,7 @@ def states_from_status(status): power_states = [] for power_state, status_map in _STATUS_MAP.iteritems(): # Skip the 'None' state - if power_state is not None: + if power_state is None: continue if status.lower() == status_map.lower(): power_states.append(power_state) -- 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') 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') 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 +- nova/tests/api/openstack/fakes.py | 3 +- nova/tests/api/openstack/test_servers.py | 204 ++++++++++++++++++++++++++++++- 3 files changed, 208 insertions(+), 4 deletions(-) (limited to 'nova') 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()) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 26b1de818..205a701ab 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -68,7 +68,8 @@ def fake_auth_init(self, application): @webob.dec.wsgify def fake_wsgi(self, req): - req.environ['nova.context'] = context.RequestContext(1, 1) + if 'nova.context' not in req.environ: + req.environ['nova.context'] = context.RequestContext(1, 1) return self.application diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1577c922b..0ca42a0b5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -724,6 +724,208 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('marker param') > -1) + def test_get_servers_with_bad_option_v1_0(self): + # 1.0 API ignores unknown options + def fake_get_all(compute_self, context, search_opts=None): + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.0/servers?unknownoption=whee') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_with_bad_option_v1_1(self): + req = webob.Request.blank('/v1.1/servers?unknownoption=whee') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + self.assertTrue(res.body.find( + "Unknown options specified: unknownoption") > -1) + + def test_get_servers_allows_image_v1_1(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('image' in search_opts) + self.assertEqual(search_opts['image'], '12345') + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?image=12345') + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_flavor_v1_1(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('flavor' in search_opts) + # flavor is an integer ID + self.assertEqual(search_opts['flavor'], 12345) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?flavor=12345') + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_status_v1_1(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('state' in search_opts) + self.assertEqual(search_opts['state'], + [power_state.RUNNING, power_state.BLOCKED]) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?status=active') + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_name_v1_1(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('name' in search_opts) + self.assertEqual(search_opts['name'], 'whee.*') + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?name=whee.*') + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_instance_name1_v1_1(self): + """Test getting servers by instance_name with admin_api + disabled + """ + FLAGS.allow_admin_api = False + req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + self.assertTrue(res.body.find( + "Unknown options specified: instance_name") > -1) + + def test_get_servers_allows_instance_name2_v1_1(self): + """Test getting servers by instance_name with admin_api + enabled but non-admin context + """ + FLAGS.allow_admin_api = True + + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=False) + req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') + req.environ["nova.context"] = context + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 403) + self.assertTrue(res.body.find( + "User does not have admin privileges") > -1) + + def test_get_servers_allows_instance_name3_v1_1(self): + """Test getting servers by instance_name with admin_api + enabled and admin context + """ + FLAGS.allow_admin_api = True + + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('instance_name' in search_opts) + self.assertEqual(search_opts['instance_name'], 'whee.*') + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') + # Request admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + req.environ["nova.context"] = context + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_ip_v1_1(self): + """Test getting servers by ip with admin_api enabled and + admin context + """ + FLAGS.allow_admin_api = True + + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('ip' in search_opts) + self.assertEqual(search_opts['ip'], '10\..*') + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?ip=10\..*') + # Request admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + req.environ["nova.context"] = context + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_allows_ip6_v1_1(self): + """Test getting servers by ip6 with admin_api enabled and + admin context + """ + FLAGS.allow_admin_api = True + + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertTrue('ip6' in search_opts) + self.assertEqual(search_opts['ip6'], 'ffff.*') + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + req = webob.Request.blank('/v1.1/servers?ip6=ffff.*') + # Request admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + req.environ["nova.context"] = context + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + def _setup_for_create_instance(self): """Shared implementation for tests below that create instance""" def instance_create(context, inst): @@ -1665,7 +1867,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) - def test_resize_server_v11(self): + def test_resize_server_v1_1(self): req = webob.Request.blank('/v1.1/servers/1/action') req.content_type = 'application/json' -- 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') 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 6ebd04c9c971e3be63cb3d6122bbca7c95004085 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 11:43:56 -0700 Subject: test fix for renamed get_by_fixed_ip call --- nova/tests/test_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index c862726ab..b9b14d1ea 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -52,7 +52,7 @@ class MetadataTestCase(test.TestCase): return '99.99.99.99' self.stubs.Set(api, 'instance_get', instance_get) - self.stubs.Set(api, 'fixed_ip_get_instance', instance_get) + self.stubs.Set(api, 'instance_get_by_fixed_ip', instance_get) self.stubs.Set(api, 'instance_get_floating_address', floating_get) self.app = metadatarequesthandler.MetadataRequestHandler() -- cgit From bc2a2f30e4b8ab92d6893ec333e756c92e96a932 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 11:48:52 -0700 Subject: pep8 fixes --- nova/compute/power_state.py | 18 ++++++++++-------- nova/db/sqlalchemy/api.py | 1 - 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index 8018c5270..bdedd8da8 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -48,14 +48,6 @@ _STATE_MAP = { BUILDING: 'building', } - -def name(code): - return _STATE_MAP[code] - - -def valid_states(): - return _STATE_MAP.keys() - _STATUS_MAP = { None: 'BUILD', NOSTATE: 'BUILD', @@ -70,10 +62,20 @@ _STATUS_MAP = { BUILDING: 'BUILD', } + +def name(code): + return _STATE_MAP[code] + + +def valid_states(): + return _STATE_MAP.keys() + + def status_from_state(power_state): """Map the power state to the server status string""" return _STATUS_MAP[power_state] + def states_from_status(status): """Map the server status string to a list of power states""" power_states = [] diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 331c58fa3..7eb724785 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1248,7 +1248,6 @@ def instance_get_all_by_column(context, column, column_data): """ session = get_session() - prefix = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload('virtual_interfaces')).\ -- 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 +++++++++----- nova/compute/api.py | 7 ------- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 'nova') 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) diff --git a/nova/compute/api.py b/nova/compute/api.py index 6675fff52..a031f8ab5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -744,10 +744,6 @@ class API(base.Base): # Search options are mutually exclusive known_params = { 'recurse_zones': (None, None), - # v1.0 API? - 'fresh': (None, None), - # v1.1 API - 'changes-since': (None, None), # Mutually exclusive options 'name': (_get_all_by_column_regexp, ('display_name',)), 'reservation_id': (_get_all_by_reservation_id, ()), @@ -762,9 +758,6 @@ class API(base.Base): 'state': (_get_all_by_column, ('state',)), 'flavor': (_get_all_by_flavor, ())} - # FIXME(comstud): 'fresh' and 'changes-since' are currently not - # implemented... - if search_opts is None: search_opts = {} -- cgit From 11101dfb47a7c3a37d3d3ec04f36e33fff9f59e2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 12:22:02 -0700 Subject: test fixes after unknown option string changes --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 0ca42a0b5..df951c5a0 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -743,7 +743,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find( - "Unknown options specified: unknownoption") > -1) + "unknown options 'unknownoption'") > -1) def test_get_servers_allows_image_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): @@ -828,7 +828,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find( - "Unknown options specified: instance_name") > -1) + "unknown options 'instance_name'") > -1) def test_get_servers_allows_instance_name2_v1_1(self): """Test getting servers by instance_name with admin_api -- cgit From ff1c882d7b12aa77895c549769a27ff4913b29c8 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 12:29:42 -0700 Subject: clarify a couple comments --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index a031f8ab5..9a606add2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -747,9 +747,9 @@ class API(base.Base): # Mutually exclusive options 'name': (_get_all_by_column_regexp, ('display_name',)), 'reservation_id': (_get_all_by_reservation_id, ()), - # Needed for EC2 API + # 'fixed_ip' needed for EC2 API 'fixed_ip': (_get_all_by_fixed_ip, ()), - # Needed for EC2 API + # 'project_id' needed for EC2 API 'project_id': (_get_all_by_project_id, ()), 'ip': (_get_all_by_ip, ()), 'ip6': (_get_all_by_ipv6, ()), -- cgit From 970b37ff9e9aef987f6e87df7d2c2e73c484e439 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 20 Jul 2011 12:35:42 -0700 Subject: missing doc strings for fixed_ip calls I renamed --- nova/db/sqlalchemy/api.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 43b9e195b..a3fc6d733 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1221,12 +1221,14 @@ def instance_get_all_by_reservation(context, reservation_id): @require_context def instance_get_by_fixed_ip(context, address): + """Return instance ref by exact match of FixedIP""" fixed_ip_ref = fixed_ip_get_by_address(context, address) return fixed_ip_ref.instance @require_context def instance_get_by_fixed_ipv6(context, address): + """Return instance ref by exact match of IPv6""" session = get_session() # convert IPv6 address to mac -- 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 ++++++++++++------------------- nova/tests/api/openstack/test_servers.py | 29 +++-- 2 files changed, 84 insertions(+), 126 deletions(-) (limited to 'nova') 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'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 821b055c4..a52940888 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1053,11 +1053,18 @@ class ServersTest(test.TestCase): self.assertEqual(servers[0]['id'], 100) def test_get_servers_with_bad_option_v1_1(self): + # 1.1 API also ignores unknown options + def fake_get_all(compute_self, context, search_opts=None): + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + req = webob.Request.blank('/v1.1/servers?unknownoption=whee') res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - self.assertTrue(res.body.find( - "unknown options 'unknownoption'") > -1) + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) def test_get_servers_allows_image_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): @@ -1134,17 +1141,6 @@ class ServersTest(test.TestCase): self.assertEqual(servers[0]['id'], 100) def test_get_servers_allows_instance_name1_v1_1(self): - """Test getting servers by instance_name with admin_api - disabled - """ - FLAGS.allow_admin_api = False - req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - self.assertTrue(res.body.find( - "unknown options 'instance_name'") > -1) - - def test_get_servers_allows_instance_name2_v1_1(self): """Test getting servers by instance_name with admin_api enabled but non-admin context """ @@ -1156,10 +1152,13 @@ class ServersTest(test.TestCase): req.environ["nova.context"] = context res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 403) + print '*' * 80 + print res.body + print '*' * 80 self.assertTrue(res.body.find( "User does not have admin privileges") > -1) - def test_get_servers_allows_instance_name3_v1_1(self): + def test_get_servers_allows_instance_name2_v1_1(self): """Test getting servers by instance_name with admin_api enabled and admin context """ -- 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') 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 450a21d8c1bed9cf6d1bcee9bcde7e88b9c3c6b9 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 00:55:33 -0700 Subject: remove debug from failing test --- nova/tests/api/openstack/test_servers.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a52940888..cdf36cabb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1152,9 +1152,6 @@ class ServersTest(test.TestCase): req.environ["nova.context"] = context res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 403) - print '*' * 80 - print res.body - print '*' * 80 self.assertTrue(res.body.find( "User does not have admin privileges") > -1) -- cgit From b5ac286fade15a61326068e5ef0959352f885efe Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 23:08:42 -0700 Subject: a lot of major re-work.. still things to finish up --- nova/compute/api.py | 157 ++++------------------ nova/db/api.py | 39 +----- nova/db/sqlalchemy/api.py | 328 ++++++++++++++-------------------------------- 3 files changed, 129 insertions(+), 395 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 5862b6d45..4d0654d20 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -677,155 +677,44 @@ class API(base.Base): all instances in the system. """ - def _get_all_by_reservation_id(reservation_id): - """Get instances by reservation ID""" - # reservation_id implies recurse_zones - search_opts['recurse_zones'] = True - return self.db.instance_get_all_by_reservation(context, - reservation_id) - - def _get_all_by_project_id(project_id): - """Get instances by project ID""" - return self.db.instance_get_all_by_project(context, project_id) - - def _get_all_by_fixed_ip(fixed_ip): - """Get instance by fixed IP""" - try: - instances = self.db.instance_get_by_fixed_ip(context, - fixed_ip) - except exception.FixedIpNotFound, e: - if search_opts.get('recurse_zones', False): - return [] - else: - raise - if not instances: - raise exception.FixedIpNotFoundForAddress(address=fixed_ip) - return instances - - def _get_all_by_instance_name(instance_name_regexp): - """Get instances by matching the Instance.name property""" - return self.db.instance_get_all_by_name_regexp( - context, instance_name_regexp) - - def _get_all_by_ip(ip_regexp): - """Get instances by matching IPv4 addresses""" - return self.db.instance_get_all_by_ip_regexp(context, ip_regexp) - - def _get_all_by_ipv6(ipv6_regexp): - """Get instances by matching IPv6 addresses""" - return self.db.instance_get_all_by_ipv6_regexp(context, - ipv6_regexp) - - def _get_all_by_column_regexp(column_regexp, column): - """Get instances by regular expression matching - Instance. - """ - return self.db.instance_get_all_by_column_regexp( - context, column, column_regexp) - - def _get_all_by_column(column_data, column): - """Get instances by exact matching Instance.""" - return self.db.instance_get_all_by_column( - context, column, column_data) - - def _get_all_by_flavor(flavor_id): - """Get instances by flavor ID""" - try: - instance_type = self.db.instance_type_get_by_flavor_id( - context, flavor_id) - except exception.FlavorNotFound: - return [] - return self.db.instance_get_all_by_column( - context, 'instance_type_id', instance_type['id']) - - # Define the search params that we will allow. This is a mapping - # of the search param to tuple of (function_to_call, (function_args)) - # A 'None' function means it's an optional parameter that will - # influence the search results, but itself is not a search option. - # Search options are mutually exclusive - known_params = { - 'recurse_zones': (None, None), - # Mutually exclusive options - 'name': (_get_all_by_column_regexp, ('display_name',)), - 'reservation_id': (_get_all_by_reservation_id, ()), - # 'fixed_ip' needed for EC2 API - 'fixed_ip': (_get_all_by_fixed_ip, ()), - # 'project_id' needed for EC2 API - 'project_id': (_get_all_by_project_id, ()), - 'ip': (_get_all_by_ip, ()), - 'ip6': (_get_all_by_ipv6, ()), - 'instance_name': (_get_all_by_instance_name, ()), - 'image': (_get_all_by_column, ('image_ref',)), - 'state': (_get_all_by_column, ('state',)), - 'flavor': (_get_all_by_flavor, ())} - if search_opts is None: search_opts = {} LOG.debug(_("Searching by: %s") % str(search_opts)) - # Mutually exclusive serach options are any options that have - # a function to call. Raise an exception if more than 1 is - # specified... - # NOTE(comstud): Ignore unknown options. The OS API will - # do it's own verification on options.. - found_opts = [opt for opt in search_opts.iterkeys() - if opt in known_params and \ - known_params[opt][0] is not None] - if len(found_opts) > 1: - found_opt_str = ", ".join(found_opts) - msg = _("More than 1 mutually exclusive " - "search option specified: %(found_opt_str)s") \ - % locals() - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - # Found a search option? - if found_opts: - found_opt = found_opts[0] - f, f_args = known_params[found_opt] - instances = f(search_opts[found_opt], *f_args) - # Nope. Return all instances if the request is in admin context.. - elif context.is_admin: - instances = self.db.instance_get_all(context) - # Nope. Return all instances for the user/project - else: - if context.project_id: - instances = self.db.instance_get_all_by_project( - context, context.project_id) + # Fixups for the DB call + filters = search_opts.copy() + if 'image' in filters: + filters['image_ref'] = filters['image'] + del filters['image'] + if 'flavor' in filters: + flavor_id = int(filters['flavor']) + try: + instance_type = self.db.instance_type_get_by_flavor_id( + context, flavor_id) + except exception.FlavorNotFound: + pass else: - instances = self.db.instance_get_all_by_user( - context, context.user_id) + filters['instance_type_id'] = instance_type['id'] + del filters['flavor'] + + recurse_zones = filters.pop('recurse_zones', False) + if 'reservation_id' in filters: + recurse_zones = True - # Convert any responses into a list of instances - if instances is None: - instances = [] - elif not isinstance(instances, list): - instances = [instances] + instances = self.db.instance_get_all_by_filters(context, filters) - if not search_opts.get('recurse_zones', False): + if not recurse_zones: return instances - new_search_opts = {} - new_search_opts.update(search_opts) - # API does state search by status, instead of the real power - # state. So if we're searching by 'state', we need to - # convert this back into 'status' - state = new_search_opts.pop('state', None) - if state: - # Might be a list.. we can only use 1. - if isinstance(state, list): - state = state[0] - new_search_opts['status'] = power_state.status_from_state( - state) - - # Recurse zones. Need admin context for this. + # Recurse zones. Need admin context for this. Send along + # the un-modified search options we received.. admin_context = context.elevated() children = scheduler_api.call_zone_method(admin_context, "list", errors_to_ignore=[novaclient.exceptions.NotFound], novaclient_collection_name="servers", - search_opts=new_search_opts) + search_opts=search_opts) for zone, servers in children: # 'servers' can be None if a 404 was returned by a zone diff --git a/nova/db/api.py b/nova/db/api.py index 22dd8a4b4..c7d5420e1 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -491,6 +491,10 @@ def instance_get_all(context): return IMPL.instance_get_all(context) +def instance_get_all_by-filters(context, filters): + """Get all instances that match all filters.""" + return IMPL.instance_get_all_by_filters(context, filters) + def instance_get_active_by_window(context, begin, end=None): """Get instances active during a certain time window.""" return IMPL.instance_get_active_by_window(context, begin, end) @@ -526,41 +530,6 @@ def instance_get_by_fixed_ipv6(context, address): return IMPL.instance_get_by_fixed_ipv6(context, address) -def instance_get_all_by_column(context, column, column_data): - """Get all instances by exact match against the specified DB column""" - return IMPL.instance_get_all_by_column(context, column, column_data) - - -def instance_get_all_by_column_regexp(context, column, column_regexp): - """Get all instances by using regular expression matching against - a particular DB column - """ - return IMPL.instance_get_all_by_column_regexp(context, - column, - column_regexp) - - -def instance_get_all_by_name_regexp(context, name_regexp): - """Get all instances by using regular expression matching against - its name - """ - return IMPL.instance_get_all_by_name_regexp(context, name_regexp) - - -def instance_get_all_by_ip_regexp(context, ip_regexp): - """Get all instances by using regular expression matching against - Floating and Fixed IP Addresses - """ - return IMPL.instance_get_all_by_ip_regexp(context, ip_regexp) - - -def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): - """Get all instances by using regular expression matching against - IPv6 Addresses - """ - return IMPL.instance_get_all_by_ipv6_regexp(context, ipv6_regexp) - - def instance_get_fixed_addresses(context, instance_id): """Get the fixed ip address of an instance.""" return IMPL.instance_get_fixed_addresses(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e4250a6cb..25a486b9c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1147,6 +1147,108 @@ def instance_get_all(context): all() +@require_context +def instance_get_all_by_filters(context, filters): + """Return instances the match all filters""" + + def _filter_by_ipv6(instance, filter_re): + for interface in instance['virtual_interfaces']: + fixed_ipv6 = interface.get('fixed_ipv6') + if fixed_ipv6 and filter_re.match(fixed_ipv6): + return True + return False + + def _filter_by_ip(instance, filter_re): + for interface in instance['virtual_interfaces']: + for fixed_ip in interface['fixed_ips']: + if not fixed_ip or not fixed_ip['address']: + continue + if filter_re.match(fixed_ip['address']): + return True + for floating_ip in fixed_ip.get('floating_ips', []): + if not floating_ip or not floating_ip['address']: + continue + if filter_re.match(floating_ip['address']): + return True + return False + + def _filter_by_display_name(instance, filter_re): + if filter_re.match(instance.display_name): + return True + return False + + def _filter_by_column(instance, filter_name, filter_re): + try: + v = getattr(instance, filter_name) + except AttributeError: + return True + if v and filter_re.match(str(v)): + return True + return False + + session = get_session() + query_prefix = session.query(models.Instance).\ + options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload_all('virtual_interfaces.network')).\ + options(joinedload_all( + 'virtual_interfaces.fixed_ips.floating_ips')).\ + options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ips.network')).\ + options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)) + + filters = filters.copy() + + if not context.is_admin: + # If we're not admin context, add appropriate filter.. + if context.project_id: + filters['project_id'] = context.project_id + else: + filters['user_id'] = context.user_id + + # Filters that we can do along with the SQL query... + query_filter_funcs = { + 'project_id': lambda query, value: query.filter_by( + project_id=value), + 'user_id': lambda query, value: query.filter_by( + user_id=value), + 'reservation_id': lambda query, value: query.filter_by( + reservation_id=value), + 'state': lambda query, value: query.filter_by(state=value)} + + query_filters = [key for key in filters.iterkeys() + if key in query_filter_funcs] + + for filter_name in query_filters: + query_prefix = query_filter_funcs[filter_name](query_prefix, + filters[filter_name]) + # Remove this from filters, so it doesn't get tried below + del filters[filter_name] + + instances = query_prefix.all() + + if not instances: + return [] + + # Now filter on everything else for regexp matching.. + filter_funcs = {'ip6': _filter_by_ipv6, + 'ip': _filter_by_ip, + 'name': _filter_by_display_name} + + for filter_name in filters.iterkeys(): + filter_func = filter_funcs.get(filter_name, None) + filter_re = re.compile(filters[filter_name]) + if filter_func: + filter_l = lambda instance: filter_func(instance, filter_re) + else: + filter_l = lambda instance: _filter_by_column(instance, + filter_name, filter_re) + instances = filter(filter_l, instances) + + return instances + + @require_admin_context def instance_get_active_by_window(context, begin, end=None): """Return instances that were continuously active over the given window""" @@ -1259,232 +1361,6 @@ def instance_get_by_fixed_ipv6(context, address): return result -@require_context -def instance_get_all_by_column(context, column, column_data): - """Get all instances by exact match against the specified DB column - 'column_data' can be a list. - """ - session = get_session() - - prefix = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) - - if isinstance(column_data, list): - column_attr = getattr(models.Instance, column) - prefix = prefix.filter(column_attr.in_(column_data)) - else: - # Set up the dictionary for filter_by() - query_filter = {} - query_filter[column] = column_data - prefix = prefix.filter_by(**query_filter) - - if context.is_admin: - all_instances = prefix.all() - elif context.project: - all_instances = prefix.\ - filter_by(project_id=context.project_id).\ - all() - else: - all_instances = prefix.\ - filter_by(user_id=context.user_id).\ - all() - if not all_instances: - return [] - - return all_instances - - -@require_context -def instance_get_all_by_column_regexp(context, column, column_regexp): - """Get all instances by using regular expression matching against - a particular DB column - """ - session = get_session() - - # MySQL 'regexp' is not portable, so we must do our own matching. - # First... grab all Instances. - prefix = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) - - if context.is_admin: - all_instances = prefix.all() - elif context.project: - all_instances = prefix.\ - filter_by(project_id=context.project_id).\ - all() - else: - all_instances = prefix.\ - filter_by(user_id=context.user_id).\ - all() - if not all_instances: - return [] - # Now do the regexp matching - compiled_regexp = re.compile(column_regexp) - instances = [] - for instance in all_instances: - v = getattr(instance, column) - if v and compiled_regexp.match(v): - instances.append(instance) - return instances - - -@require_context -def instance_get_all_by_name_regexp(context, name_regexp): - """Get all instances by using regular expression matching against - its name - """ - - session = get_session() - - # MySQL 'regexp' is not portable, so we must do our own matching. - # First... grab all Instances. - prefix = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) - - if context.is_admin: - all_instances = prefix.all() - elif context.project: - all_instances = prefix.\ - filter_by(project_id=context.project_id).\ - all() - else: - all_instances = prefix.\ - filter_by(user_id=context.user_id).\ - all() - if not all_instances: - return [] - # Now do the regexp matching - compiled_regexp = re.compile(name_regexp) - return [instance for instance in all_instances - if compiled_regexp.match(instance.name)] - - -@require_context -def instance_get_all_by_ip_regexp(context, ip_regexp): - """Get all instances by using regular expression matching against - Floating and Fixed IP Addresses - """ - session = get_session() - - # Query both FixedIp and FloatingIp tables to get matches. - # Since someone could theoretically search for something that matches - # instances in both tables... we need to use a dictionary keyed - # on instance ID to make sure we return only 1. We can't key off - # of 'instance' because it's just a reference and will be different - # addresses even though they might point to the same instance ID. - instances = {} - - fixed_ips = session.query(models.FixedIp).\ - options(joinedload_all('instance.fixed_ips.floating_ips')).\ - options(joinedload('instance.virtual_interfaces')).\ - options(joinedload('instance.security_groups')).\ - options(joinedload_all('instance.fixed_ips.network')).\ - options(joinedload('instance.metadata')).\ - options(joinedload('instance.instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() - floating_ips = session.query(models.FloatingIp).\ - options(joinedload_all( - 'fixed_ip.instance.fixed_ips.floating_ips')).\ - options(joinedload('fixed_ip.instance.virtual_interfaces')).\ - options(joinedload('fixed_ip.instance.security_groups')).\ - options(joinedload_all( - 'fixed_ip.instance.fixed_ips.network')).\ - options(joinedload('fixed_ip.instance.metadata')).\ - options(joinedload('fixed_ip.instance.instance_type')).\ - filter_by(deleted=can_read_deleted(context)).\ - all() - - if fixed_ips is None: - fixed_ips = [] - if floating_ips is None: - floating_ips = [] - - compiled_regexp = re.compile(ip_regexp) - instances = {} - - # Now do the regexp matching - for fixed_ip in fixed_ips: - if fixed_ip.instance and compiled_regexp.match(fixed_ip.address): - instances[fixed_ip.instance.uuid] = fixed_ip.instance - for floating_ip in floating_ips: - fixed_ip = floating_ip.fixed_ip - if fixed_ip and fixed_ip.instance and\ - compiled_regexp.match(floating_ip.address): - instances[fixed_ip.instance.uuid] = fixed_ip.instance - - if context.is_admin: - return instances.values() - elif context.project: - return [instance for instance in instances.values() - if instance.project_id == context.project_id] - else: - return [instance for instance in instances.values() - if instance.user_id == context.user_id] - - return instances.values() - - -@require_context -def instance_get_all_by_ipv6_regexp(context, ipv6_regexp): - """Get all instances by using regular expression matching against - IPv6 Addresses - """ - - session = get_session() - with session.begin(): - prefix = session.query(models.Instance).\ - options(joinedload_all('fixed_ips.floating_ips')).\ - options(joinedload('virtual_interfaces')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ips.network')).\ - options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) - - if context.is_admin: - all_instances = prefix.all() - elif context.project: - all_instances = prefix.\ - filter_by(project_id=context.project_id).\ - all() - else: - all_instances = prefix.\ - filter_by(user_id=context.user_id).\ - all() - - if not all_instances: - return [] - - instances = [] - compiled_regexp = re.compile(ipv6_regexp) - for instance in all_instances: - ipv6_addrs = _ipv6_get_by_instance_ref(context, instance) - for ipv6 in ipv6_addrs: - if compiled_regexp.match(ipv6): - instances.append(instance) - break - return instances - - @require_admin_context def instance_get_project_vpn(context, project_id): session = get_session() -- 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 ++++++++++++++++++------------------------ nova/db/sqlalchemy/api.py | 7 +-- 2 files changed, 49 insertions(+), 62 deletions(-) (limited to 'nova') 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): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 25a486b9c..04c9273b0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1149,7 +1149,9 @@ def instance_get_all(context): @require_context def instance_get_all_by_filters(context, filters): - """Return instances the match all filters""" + """Return instances the match all filters. Deleted instances + will be returned by default, unless there's a filter that says + otherwise""" def _filter_by_ipv6(instance, filter_re): for interface in instance['virtual_interfaces']: @@ -1195,8 +1197,7 @@ def instance_get_all_by_filters(context, filters): options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ - options(joinedload('instance_type')).\ - filter_by(deleted=can_read_deleted(context)) + options(joinedload('instance_type')) filters = filters.copy() -- cgit From b9ecf869761ee0506872b0d44d93d453be4c3477 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 01:45:42 -0700 Subject: typos --- nova/compute/api.py | 4 ++-- nova/db/api.py | 2 +- nova/db/sqlalchemy/api.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 4d0654d20..4d577b578 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -725,8 +725,8 @@ class API(base.Base): server._info['_is_precooked'] = True instances.append(server._info) - # Fixed IP returns a FixedIpNotFound when an instance is not - # found... + # fixed_ip searching should return a FixedIpNotFound exception + # when an instance is not found... fixed_ip = search_opts.get('fixed_ip', None) if fixed_ip and not instances: raise exception.FixedIpNotFoundForAddress(address=fixed_ip) diff --git a/nova/db/api.py b/nova/db/api.py index c7d5420e1..23fac9921 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -491,7 +491,7 @@ def instance_get_all(context): return IMPL.instance_get_all(context) -def instance_get_all_by-filters(context, filters): +def instance_get_all_by_filters(context, filters): """Get all instances that match all filters.""" return IMPL.instance_get_all_by_filters(context, filters) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 04c9273b0..55c804ae9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1149,7 +1149,7 @@ def instance_get_all(context): @require_context def instance_get_all_by_filters(context, filters): - """Return instances the match all filters. Deleted instances + """Return instances that match all filters. Deleted instances will be returned by default, unless there's a filter that says otherwise""" -- 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 +- nova/compute/power_state.py | 31 -------------------- nova/db/sqlalchemy/api.py | 49 +++++++++++++++++++------------- nova/tests/api/openstack/fakes.py | 2 -- nova/tests/api/openstack/test_servers.py | 13 +++++---- 7 files changed, 75 insertions(+), 65 deletions(-) (limited to 'nova') 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() diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index bdedd8da8..c468fe6b3 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -48,20 +48,6 @@ _STATE_MAP = { BUILDING: 'building', } -_STATUS_MAP = { - None: 'BUILD', - NOSTATE: 'BUILD', - RUNNING: 'ACTIVE', - BLOCKED: 'ACTIVE', - SUSPENDED: 'SUSPENDED', - PAUSED: 'PAUSED', - SHUTDOWN: 'SHUTDOWN', - SHUTOFF: 'SHUTOFF', - CRASHED: 'ERROR', - FAILED: 'ERROR', - BUILDING: 'BUILD', -} - def name(code): return _STATE_MAP[code] @@ -69,20 +55,3 @@ def name(code): def valid_states(): return _STATE_MAP.keys() - - -def status_from_state(power_state): - """Map the power state to the server status string""" - return _STATUS_MAP[power_state] - - -def 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 diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55c804ae9..f8920e62c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1153,14 +1153,14 @@ def instance_get_all_by_filters(context, filters): will be returned by default, unless there's a filter that says otherwise""" - def _filter_by_ipv6(instance, filter_re): + def _regexp_filter_by_ipv6(instance, filter_re): for interface in instance['virtual_interfaces']: fixed_ipv6 = interface.get('fixed_ipv6') if fixed_ipv6 and filter_re.match(fixed_ipv6): return True return False - def _filter_by_ip(instance, filter_re): + def _regexp_filter_by_ip(instance, filter_re): for interface in instance['virtual_interfaces']: for fixed_ip in interface['fixed_ips']: if not fixed_ip or not fixed_ip['address']: @@ -1174,12 +1174,12 @@ def instance_get_all_by_filters(context, filters): return True return False - def _filter_by_display_name(instance, filter_re): + def _regexp_filter_by_display_name(instance, filter_re): if filter_re.match(instance.display_name): return True return False - def _filter_by_column(instance, filter_name, filter_re): + def _regexp_filter_by_column(instance, filter_name, filter_re): try: v = getattr(instance, filter_name) except AttributeError: @@ -1188,6 +1188,18 @@ def instance_get_all_by_filters(context, filters): return True return False + def _exact_match_filter(query, column, value): + """Do exact match against a column. value to match can be a list + so you can match any value in the list. + """ + if isinstance(value, list): + column_attr = getattr(models.Instance, column) + return query.filter(column_attr.in_(value)) + else: + filter_dict = {} + filter_dict[column] = value + return query.filter_by(**filter_dict) + session = get_session() query_prefix = session.query(models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ @@ -1208,21 +1220,16 @@ def instance_get_all_by_filters(context, filters): else: filters['user_id'] = context.user_id - # Filters that we can do along with the SQL query... - query_filter_funcs = { - 'project_id': lambda query, value: query.filter_by( - project_id=value), - 'user_id': lambda query, value: query.filter_by( - user_id=value), - 'reservation_id': lambda query, value: query.filter_by( - reservation_id=value), - 'state': lambda query, value: query.filter_by(state=value)} + # Filters for exact matches that we can do along with the SQL query... + # For other filters that don't match this, we will do regexp matching + exact_match_filter_names = ['project_id', 'user_id', 'image_ref', + 'state', 'instance_type_id', 'deleted'] query_filters = [key for key in filters.iterkeys() - if key in query_filter_funcs] + if key in exact_match_filter_names] for filter_name in query_filters: - query_prefix = query_filter_funcs[filter_name](query_prefix, + query_prefix = _exact_match_filter(query_prefix, filter_name, filters[filter_name]) # Remove this from filters, so it doesn't get tried below del filters[filter_name] @@ -1233,17 +1240,19 @@ def instance_get_all_by_filters(context, filters): return [] # Now filter on everything else for regexp matching.. - filter_funcs = {'ip6': _filter_by_ipv6, - 'ip': _filter_by_ip, - 'name': _filter_by_display_name} + # For filters not in the list, we'll attempt to use the filter_name + # as a column name in Instance.. + regexp_filter_funcs = {'ip6': _regexp_filter_by_ipv6, + 'ip': _regexp_filter_by_ip, + 'name': _regexp_filter_by_display_name} for filter_name in filters.iterkeys(): - filter_func = filter_funcs.get(filter_name, None) + filter_func = regexp_filter_funcs.get(filter_name, None) filter_re = re.compile(filters[filter_name]) if filter_func: filter_l = lambda instance: filter_func(instance, filter_re) else: - filter_l = lambda instance: _filter_by_column(instance, + filter_l = lambda instance: _regexp_filter_by_column(instance, filter_name, filter_re) instances = filter(filter_l, instances) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 33419d359..a67a28a4e 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -68,8 +68,6 @@ def fake_auth_init(self, application): @webob.dec.wsgify def fake_wsgi(self, req): - if 'nova.context' not in req.environ: - req.environ['nova.context'] = context.RequestContext(1, 1) return self.application diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cc439f024..2ae45b791 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1149,6 +1149,7 @@ class ServersTest(test.TestCase): return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=False) req = webob.Request.blank('/v1.1/servers?image=12345') res = req.get_response(fakes.wsgi_app()) @@ -1168,6 +1169,7 @@ class ServersTest(test.TestCase): return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=False) req = webob.Request.blank('/v1.1/servers?flavor=12345') res = req.get_response(fakes.wsgi_app()) @@ -1187,6 +1189,7 @@ class ServersTest(test.TestCase): return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=False) req = webob.Request.blank('/v1.1/servers?status=active') res = req.get_response(fakes.wsgi_app()) @@ -1205,6 +1208,7 @@ class ServersTest(test.TestCase): return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=False) req = webob.Request.blank('/v1.1/servers?name=whee.*') res = req.get_response(fakes.wsgi_app()) @@ -1219,8 +1223,7 @@ class ServersTest(test.TestCase): """Test getting servers by instance_name with admin_api enabled but non-admin context """ - FLAGS.allow_admin_api = True - + self.flags(allow_admin_api=True) context = nova.context.RequestContext('testuser', 'testproject', is_admin=False) req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') @@ -1234,8 +1237,6 @@ class ServersTest(test.TestCase): """Test getting servers by instance_name with admin_api enabled and admin context """ - FLAGS.allow_admin_api = True - def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) self.assertTrue('instance_name' in search_opts) @@ -1243,6 +1244,7 @@ class ServersTest(test.TestCase): return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=True) req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') # Request admin context @@ -1878,6 +1880,7 @@ class ServersTest(test.TestCase): def test_get_all_server_details_v1_0(self): req = webob.Request.blank('/v1.0/servers/detail') res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) for i, s in enumerate(res_dict['servers']): @@ -1933,7 +1936,7 @@ class ServersTest(test.TestCase): return [stub_instance(i, 'fake', 'fake', None, None, i % 2) for i in xrange(5)] - self.stubs.Set(nova.db.api, 'instance_get_all_by_project', + self.stubs.Set(nova.db.api, 'instance_get_all_by_filters', return_servers_with_host) req = webob.Request.blank('/v1.0/servers/detail') -- 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 ++ nova/tests/api/openstack/fakes.py | 8 +- nova/tests/api/openstack/test_servers.py | 124 ++++++++++++++++++++++++------- 3 files changed, 109 insertions(+), 28 deletions(-) (limited to 'nova') 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: diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a67a28a4e..d11fbf788 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -71,14 +71,18 @@ def fake_wsgi(self, req): return self.application -def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True): +def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, + fake_auth_context=None): if not inner_app10: inner_app10 = openstack.APIRouterV10() if not inner_app11: inner_app11 = openstack.APIRouterV11() if fake_auth: - ctxt = context.RequestContext('fake', 'fake') + if fake_auth_context is not None: + ctxt = fake_auth_context + else: + ctxt = context.RequestContext('fake', 'fake') api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, limits.RateLimitingMiddleware(inner_app10))) api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2ae45b791..cc2afa57c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -240,7 +240,8 @@ class ServersTest(test.TestCase): fakes.stub_out_key_pair_funcs(self.stubs) fakes.stub_out_image_service(self.stubs) self.stubs.Set(utils, 'gen_uuid', fake_gen_uuid) - self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) + self.stubs.Set(nova.db.api, 'instance_get_all_by_filters', + return_servers) self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_by_uuid) @@ -1165,7 +1166,7 @@ class ServersTest(test.TestCase): self.assertNotEqual(search_opts, None) self.assertTrue('flavor' in search_opts) # flavor is an integer ID - self.assertEqual(search_opts['flavor'], 12345) + self.assertEqual(search_opts['flavor'], '12345') return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) @@ -1200,6 +1201,18 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) + def test_get_servers_invalid_status_v1_1(self): + """Test getting servers by invalid status""" + + self.flags(allow_admin_api=False) + + req = webob.Request.blank('/v1.1/servers?status=running') + res = req.get_response(fakes.wsgi_app()) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 400) + self.assertTrue(res.body.find('Invalid server status') > -1) + def test_get_servers_allows_name_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) @@ -1219,39 +1232,100 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) - def test_get_servers_allows_instance_name1_v1_1(self): - """Test getting servers by instance_name with admin_api - enabled but non-admin context + def test_get_servers_unknown_or_admin_options1(self): + """Test getting servers by admin-only or unknown options. + This tests when admin_api is off. Make sure the admin and + unknown options are stripped before they get to + compute_api.get_all() + """ + + self.flags(allow_admin_api=False) + + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + # Allowed by user + self.assertTrue('name' in search_opts) + self.assertTrue('status' in search_opts) + # Allowed only by admins with admin API on + self.assertFalse('ip' in search_opts) + self.assertFalse('unknown_option' in search_opts) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" + req = webob.Request.blank('/v1.1/servers?%s' % query_str) + # Request admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) + + def test_get_servers_unknown_or_admin_options2(self): + """Test getting servers by admin-only or unknown options. + This tests when admin_api is on, but context is a user. + Make sure the admin and unknown options are stripped before + they get to compute_api.get_all() """ + self.flags(allow_admin_api=True) + + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + # Allowed by user + self.assertTrue('name' in search_opts) + self.assertTrue('status' in search_opts) + # Allowed only by admins with admin API on + self.assertFalse('ip' in search_opts) + self.assertFalse('unknown_option' in search_opts) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + + query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" + req = webob.Request.blank('/v1.1/servers?%s' % query_str) + # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=False) - req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') - req.environ["nova.context"] = context - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 403) - self.assertTrue(res.body.find( - "User does not have admin privileges") > -1) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) + # The following assert will fail if either of the asserts in + # fake_get_all() fail + self.assertEqual(res.status_int, 200) + servers = json.loads(res.body)['servers'] + self.assertEqual(len(servers), 1) + self.assertEqual(servers[0]['id'], 100) - def test_get_servers_allows_instance_name2_v1_1(self): - """Test getting servers by instance_name with admin_api - enabled and admin context + def test_get_servers_unknown_or_admin_options3(self): + """Test getting servers by admin-only or unknown options. + This tests when admin_api is on and context is admin. + All options should be passed through to compute_api.get_all() """ + + self.flags(allow_admin_api=True) + def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) - self.assertTrue('instance_name' in search_opts) - self.assertEqual(search_opts['instance_name'], 'whee.*') + # Allowed by user + self.assertTrue('name' in search_opts) + self.assertTrue('status' in search_opts) + # Allowed only by admins with admin API on + self.assertTrue('ip' in search_opts) + self.assertTrue('unknown_option' in search_opts) return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - self.flags(allow_admin_api=True) - req = webob.Request.blank('/v1.1/servers?instance_name=whee.*') + query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" + req = webob.Request.blank('/v1.1/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) - req.environ["nova.context"] = context - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) # The following assert will fail if either of the asserts in # fake_get_all() fail self.assertEqual(res.status_int, 200) @@ -1259,7 +1333,7 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) - def test_get_servers_allows_ip_v1_1(self): + def test_get_servers_admin_allows_ip_v1_1(self): """Test getting servers by ip with admin_api enabled and admin context """ @@ -1277,8 +1351,7 @@ class ServersTest(test.TestCase): # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) - req.environ["nova.context"] = context - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) # The following assert will fail if either of the asserts in # fake_get_all() fail self.assertEqual(res.status_int, 200) @@ -1286,7 +1359,7 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) - def test_get_servers_allows_ip6_v1_1(self): + def test_get_servers_admin_allows_ip6_v1_1(self): """Test getting servers by ip6 with admin_api enabled and admin context """ @@ -1304,8 +1377,7 @@ class ServersTest(test.TestCase): # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) - req.environ["nova.context"] = context - res = req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) # The following assert will fail if either of the asserts in # fake_get_all() fail self.assertEqual(res.status_int, 200) -- 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') 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 04a2a64d42e6acf0addd8918acd3139dc4aff7c7 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 15:04:36 -0700 Subject: resolved conflict incorrectly from trunk merge --- nova/tests/api/openstack/test_servers.py | 193 ------------------------------- 1 file changed, 193 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 37546dab4..41bbb91f5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2142,199 +2142,6 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 204) self.assertEqual(self.server_delete_called, True) - def test_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_server_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/action') - req.content_type = 'application/json' - req.method = 'POST' - body_dict = { - "resize": { - "flavorRef": 3, - }, - } - req.body = json.dumps(body_dict) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_bad_flavor_data(self): - req = self.webreq('/1/action', 'POST', {"resize": "bad_data"}) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - self.assertEqual(self.resize_called, False) - - def test_resize_invalid_flavorid(self): - req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": 300}}) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_resize_nonint_flavorid(self): - req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": "a"}}) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_resize_invalid_flavorid_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/action') - req.content_type = 'application/json' - req.method = 'POST' - resize_body = { - "resize": { - "image": { - "id": 300, - }, - }, - } - req.body = json.dumps(resize_body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_resize_nonint_flavorid_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/action') - req.content_type = 'application/json' - req.method = 'POST' - resize_body = { - "resize": { - "image": { - "id": "a", - }, - }, - } - req.body = json.dumps(resize_body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_resize_raises_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - def resize_mock(*args): - raise Exception("An error occurred.") - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 500) - - def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET') - - def fake_migration_get(*args): - return {} - - self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', - fake_migration_get) - res = req.get_response(fakes.wsgi_app()) - body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') - - def test_confirm_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - self.resize_called = False - - def confirm_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 204) - self.assertEqual(self.resize_called, True) - - def test_confirm_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - def confirm_resize_mock(*args): - raise Exception("An error occurred.") - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_revert_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - self.resize_called = False - - def revert_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_revert_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - def revert_resize_mock(*args): - raise Exception("An error occurred.") - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_migrate_server(self): - """This is basically the same as resize, only we provide the `migrate` - attribute in the body's dict. - """ - req = self.webreq('/1/migrate', 'POST') - - FLAGS.allow_admin_api = True - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_migrate_server_no_admin_api_fails(self): - req = self.webreq('/1/migrate', 'POST') - - FLAGS.allow_admin_api = False - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 404) - def test_shutdown_status(self): new_server = return_server_with_power_state(power_state.SHUTDOWN) self.stubs.Set(nova.db.api, 'instance_get', new_server) -- cgit From 6e791e8b773565b62c4b8ba35cec455cb8c13ac8 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 16:30:55 -0700 Subject: test fixes.. one more to go --- nova/compute/api.py | 38 ++++++++++++++++++++++++++++---------- nova/db/sqlalchemy/api.py | 8 +------- nova/tests/test_compute.py | 35 ++++++++++++++++++++++++++--------- 3 files changed, 55 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 02e9f3e06..382f3c541 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -684,25 +684,43 @@ class API(base.Base): # Fixups for the DB call filters = search_opts.copy() + recurse_zones = filters.pop('recurse_zones', False) if 'image' in filters: filters['image_ref'] = filters['image'] del filters['image'] + invalid_flavor = False if 'flavor' in filters: - flavor_id = int(filters['flavor']) - try: - instance_type = self.db.instance_type_get_by_flavor_id( - context, flavor_id) - except exception.FlavorNotFound: - pass - else: - filters['instance_type_id'] = instance_type['id'] + instance_type = self.db.instance_type_get_by_flavor_id( + context, filters['flavor']) + filters['instance_type_id'] = instance_type['id'] del filters['flavor'] + # 'name' means Instance.display_name + # 'instance_name' means Instance.name + if 'name' in filters: + filters['display_name'] = filters['name'] + del filters['name'] + if 'instance_name' in filters: + filters['name'] = filters['instance_name'] + del filters['instance_name'] - recurse_zones = filters.pop('recurse_zones', False) if 'reservation_id' in filters: recurse_zones = True - instances = self.db.instance_get_all_by_filters(context, filters) + if 'fixed_ip' in search_opts: + # special cased for ec2. we end up ignoring all other + # search options. + try: + instance = self.db.instance_get_by_fixed_ip(context, + search_opts['fixed_ip']) + except exception.FloatingIpNotFound, e: + if not recurse_zones: + raise + if instance: + return [instance] + instances = [] + # fall through + else: + instances = self.db.instance_get_all_by_filters(context, filters) if not recurse_zones: return instances diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index f8920e62c..4d724dbdb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1174,11 +1174,6 @@ def instance_get_all_by_filters(context, filters): return True return False - def _regexp_filter_by_display_name(instance, filter_re): - if filter_re.match(instance.display_name): - return True - return False - def _regexp_filter_by_column(instance, filter_name, filter_re): try: v = getattr(instance, filter_name) @@ -1243,8 +1238,7 @@ def instance_get_all_by_filters(context, filters): # For filters not in the list, we'll attempt to use the filter_name # as a column name in Instance.. regexp_filter_funcs = {'ip6': _regexp_filter_by_ipv6, - 'ip': _regexp_filter_by_ip, - 'name': _regexp_filter_by_display_name} + 'ip': _regexp_filter_by_ip} for filter_name in filters.iterkeys(): filter_func = regexp_filter_funcs.get(filter_name, None) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 6732df154..0957981ed 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -980,7 +980,7 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) - def test_get_all_by_ip_regex(self): + def test_get_all_by_ip_regexp(self): """Test searching by Floating and Fixed IP""" c = context.get_admin_context() instance_id1 = self._create_instance({'server_name': 'woot'}) @@ -991,20 +991,34 @@ class ComputeTestCase(test.TestCase): 'server_name': 'not-woot', 'id': 30}) + vif_ref1 = db.virtual_interface_create(c, + {'instance_id': instance_id1, + 'network_id': 1}) + vif_ref2 = db.virtual_interface_create(c, + {'instance_id': instance_id2, + 'network_id': 2}) + vif_ref3 = db.virtual_interface_create(c, + {'instance_id': instance_id3, + 'network_id': 3}) + db.fixed_ip_create(c, {'address': '1.1.1.1', - 'instance_id': instance_id1}) + 'instance_id': instance_id1, + 'virtual_interface_id': vif_ref1['id']}) db.fixed_ip_create(c, {'address': '1.1.2.1', - 'instance_id': instance_id2}) + 'instance_id': instance_id2, + 'virtual_interface_id': vif_ref2['id']}) fix_addr = db.fixed_ip_create(c, {'address': '1.1.3.1', - 'instance_id': instance_id3}) + 'instance_id': instance_id3, + 'virtual_interface_id': vif_ref3['id']}) fix_ref = db.fixed_ip_get_by_address(c, fix_addr) flo_ref = db.floating_ip_create(c, {'address': '10.0.0.2', 'fixed_ip_id': fix_ref['id']}) + # ends up matching 2nd octet here.. so all 3 match instances = self.compute_api.get_all(c, search_opts={'ip': '.*\.1'}) self.assertEqual(len(instances), 3) @@ -1029,12 +1043,15 @@ class ComputeTestCase(test.TestCase): self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id3) + db.virtual_interface_delete(c, vif_ref1['id']) + db.virtual_interface_delete(c, vif_ref2['id']) + db.virtual_interface_delete(c, vif_ref3['id']) + db.floating_ip_destroy(c, '10.0.0.2') db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) - db.floating_ip_destroy(c, '10.0.0.2') - def test_get_all_by_ipv6_regex(self): + def test_get_all_by_ipv6_regexp(self): """Test searching by IPv6 address""" def fake_ipv6_get_by_instance_ref(context, instance): if instance.id == 1: @@ -1144,9 +1161,9 @@ class ComputeTestCase(test.TestCase): search_opts={'flavor': 5}) self.assertEqual(len(instances), 0) - instances = self.compute_api.get_all(c, - search_opts={'flavor': 99}) - self.assertEqual(len(instances), 0) + self.assertRaises(exception.FlavorNotFound, + self.compute_api.get_all, + c, search_opts={'flavor': 99}) instances = self.compute_api.get_all(c, search_opts={'flavor': 3}) -- cgit From d722d6f635c99a758910f24e7681753599894e70 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 17:09:36 -0700 Subject: fix ipv6 search test and add test for multiple options at once --- nova/tests/test_compute.py | 142 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 0957981ed..7792f5909 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -983,23 +983,26 @@ class ComputeTestCase(test.TestCase): def test_get_all_by_ip_regexp(self): """Test searching by Floating and Fixed IP""" c = context.get_admin_context() - instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id1 = self._create_instance({'display_name': 'woot'}) instance_id2 = self._create_instance({ - 'server_name': 'woo', + 'display_name': 'woo', 'id': 20}) instance_id3 = self._create_instance({ - 'server_name': 'not-woot', + 'display_name': 'not-woot', 'id': 30}) vif_ref1 = db.virtual_interface_create(c, - {'instance_id': instance_id1, + {'address': '12:34:56:78:90:12', + 'instance_id': instance_id1, 'network_id': 1}) vif_ref2 = db.virtual_interface_create(c, - {'instance_id': instance_id2, - 'network_id': 2}) + {'address': '90:12:34:56:78:90', + 'instance_id': instance_id2, + 'network_id': 1}) vif_ref3 = db.virtual_interface_create(c, - {'instance_id': instance_id3, - 'network_id': 3}) + {'address': '34:56:78:90:12:34', + 'instance_id': instance_id3, + 'network_id': 1}) db.fixed_ip_create(c, {'address': '1.1.1.1', @@ -1053,35 +1056,41 @@ class ComputeTestCase(test.TestCase): def test_get_all_by_ipv6_regexp(self): """Test searching by IPv6 address""" - def fake_ipv6_get_by_instance_ref(context, instance): - if instance.id == 1: - return ['ffff:ffff::1'] - if instance.id == 20: - return ['dddd:dddd::1'] - if instance.id == 30: - return ['cccc:cccc::1', 'eeee:eeee::1', 'dddd:dddd::1'] - - self.stubs.Set(sqlalchemy_api, '_ipv6_get_by_instance_ref', - fake_ipv6_get_by_instance_ref) c = context.get_admin_context() - instance_id1 = self._create_instance({'server_name': 'woot'}) + instance_id1 = self._create_instance({'display_name': 'woot'}) instance_id2 = self._create_instance({ - 'server_name': 'woo', + 'display_name': 'woo', 'id': 20}) instance_id3 = self._create_instance({ - 'server_name': 'not-woot', + 'display_name': 'not-woot', 'id': 30}) + vif_ref1 = db.virtual_interface_create(c, + {'address': '12:34:56:78:90:12', + 'instance_id': instance_id1, + 'network_id': 1}) + vif_ref2 = db.virtual_interface_create(c, + {'address': '90:12:34:56:78:90', + 'instance_id': instance_id2, + 'network_id': 1}) + vif_ref3 = db.virtual_interface_create(c, + {'address': '34:56:78:90:12:34', + 'instance_id': instance_id3, + 'network_id': 1}) + + # This will create IPv6 addresses of: + # 1: fd00::1034:56ff:fe78:9012 + # 20: fd00::9212:34ff:fe56:7890 + # 30: fd00::3656:78ff:fe90:1234 + instances = self.compute_api.get_all(c, - search_opts={'ip6': 'ff.*'}) + search_opts={'ip6': '.*1034.*'}) self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id1) - instance_ids = [instance.id for instance in instances] - self.assertTrue(instance_id1 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'ip6': '.*::1'}) + search_opts={'ip6': '^fd00.*'}) self.assertEqual(len(instances), 3) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id1 in instance_ids) @@ -1089,12 +1098,93 @@ class ComputeTestCase(test.TestCase): self.assertTrue(instance_id3 in instance_ids) instances = self.compute_api.get_all(c, - search_opts={'ip6': '.*dd:.*'}) + search_opts={'ip6': '^.*12.*34.*'}) self.assertEqual(len(instances), 2) instance_ids = [instance.id for instance in instances] self.assertTrue(instance_id2 in instance_ids) self.assertTrue(instance_id3 in instance_ids) + db.virtual_interface_delete(c, vif_ref1['id']) + db.virtual_interface_delete(c, vif_ref2['id']) + db.virtual_interface_delete(c, vif_ref3['id']) + db.instance_destroy(c, instance_id1) + db.instance_destroy(c, instance_id2) + db.instance_destroy(c, instance_id3) + + def test_get_all_by_multiple_options_at_once(self): + """Test searching by multiple options at once""" + c = context.get_admin_context() + instance_id1 = self._create_instance({'display_name': 'woot'}) + instance_id2 = self._create_instance({ + 'display_name': 'woo', + 'id': 20}) + instance_id3 = self._create_instance({ + 'display_name': 'not-woot', + 'id': 30}) + + vif_ref1 = db.virtual_interface_create(c, + {'address': '12:34:56:78:90:12', + 'instance_id': instance_id1, + 'network_id': 1}) + vif_ref2 = db.virtual_interface_create(c, + {'address': '90:12:34:56:78:90', + 'instance_id': instance_id2, + 'network_id': 1}) + vif_ref3 = db.virtual_interface_create(c, + {'address': '34:56:78:90:12:34', + 'instance_id': instance_id3, + 'network_id': 1}) + + db.fixed_ip_create(c, + {'address': '1.1.1.1', + 'instance_id': instance_id1, + 'virtual_interface_id': vif_ref1['id']}) + db.fixed_ip_create(c, + {'address': '1.1.2.1', + 'instance_id': instance_id2, + 'virtual_interface_id': vif_ref2['id']}) + fix_addr = db.fixed_ip_create(c, + {'address': '1.1.3.1', + 'instance_id': instance_id3, + 'virtual_interface_id': vif_ref3['id']}) + fix_ref = db.fixed_ip_get_by_address(c, fix_addr) + flo_ref = db.floating_ip_create(c, + {'address': '10.0.0.2', + 'fixed_ip_id': fix_ref['id']}) + + # ip ends up matching 2nd octet here.. so all 3 match ip + # but 'name' only matches one + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*\.1', 'name': 'not.*'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id3) + + # ip ends up matching any ip with a '2' in it.. so instance + # 2 and 3.. but name should only match #2 + # but 'name' only matches one + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*2', 'name': '^woo.*'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id2) + + # same as above but no match on name (name matches instance_id1 + # but the ip query doesn't + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*2.*', 'name': '^woot.*'}) + self.assertEqual(len(instances), 0) + + # ip matches all 3... ipv6 matches #2+#3...name matches #3 + instances = self.compute_api.get_all(c, + search_opts={'ip': '.*\.1', + 'name': 'not.*', + 'ip6': '^.*12.*34.*'}) + self.assertEqual(len(instances), 1) + self.assertEqual(instances[0].id, instance_id3) + + db.virtual_interface_delete(c, vif_ref1['id']) + db.virtual_interface_delete(c, vif_ref2['id']) + db.virtual_interface_delete(c, vif_ref3['id']) + db.floating_ip_destroy(c, '10.0.0.2') db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) db.instance_destroy(c, instance_id3) -- 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 - nova/db/api.py | 1 + nova/db/sqlalchemy/api.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') 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: diff --git a/nova/db/api.py b/nova/db/api.py index 23fac9921..4c8f25f5d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -495,6 +495,7 @@ def instance_get_all_by_filters(context, filters): """Get all instances that match all filters.""" return IMPL.instance_get_all_by_filters(context, filters) + def instance_get_active_by_window(context, begin, end=None): """Get instances active during a certain time window.""" return IMPL.instance_get_active_by_window(context, begin, end) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4d724dbdb..36fae1be1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1205,7 +1205,7 @@ def instance_get_all_by_filters(context, filters): options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')) - + filters = filters.copy() if not context.is_admin: -- 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') 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 4d0125b34a4796fd6d3312a4144a0834ba318469 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 17:50:59 -0700 Subject: convert filter value to a string just in case before running re.compile --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 36fae1be1..d6e7204b4 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1242,7 +1242,7 @@ def instance_get_all_by_filters(context, filters): for filter_name in filters.iterkeys(): filter_func = regexp_filter_funcs.get(filter_name, None) - filter_re = re.compile(filters[filter_name]) + filter_re = re.compile(str(filters[filter_name])) if filter_func: filter_l = lambda instance: filter_func(instance, filter_re) else: -- 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') 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 ++-- nova/db/sqlalchemy/api.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') 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( diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d6e7204b4..65a1c19a1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1206,6 +1206,8 @@ def instance_get_all_by_filters(context, filters): options(joinedload('metadata')).\ options(joinedload('instance_type')) + # Make a copy of the filters dictionary to use going forward, as we'll + # be modifying it and we shouldn't affect the caller's use of it. filters = filters.copy() if not context.is_admin: @@ -1224,10 +1226,10 @@ def instance_get_all_by_filters(context, filters): if key in exact_match_filter_names] for filter_name in query_filters: + # Do the matching and remove the filter from the dictionary + # so we don't try it again below.. query_prefix = _exact_match_filter(query_prefix, filter_name, - filters[filter_name]) - # Remove this from filters, so it doesn't get tried below - del filters[filter_name] + filters.pop(filter_name)) instances = query_prefix.all() -- 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') 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 +- nova/compute/api.py | 75 +++++++++++++++++++++++----------------------- nova/tests/test_compute.py | 27 ++++++++++++----- 3 files changed, 57 insertions(+), 47 deletions(-) (limited to 'nova') 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 diff --git a/nova/compute/api.py b/nova/compute/api.py index 382f3c541..7a3ff0c56 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -683,44 +683,49 @@ class API(base.Base): LOG.debug(_("Searching by: %s") % str(search_opts)) # Fixups for the DB call - filters = search_opts.copy() - recurse_zones = filters.pop('recurse_zones', False) - if 'image' in filters: - filters['image_ref'] = filters['image'] - del filters['image'] - invalid_flavor = False - if 'flavor' in filters: + filters = {} + + def _remap_flavor_filter(flavor_id): instance_type = self.db.instance_type_get_by_flavor_id( - context, filters['flavor']) + context, flavor_id) filters['instance_type_id'] = instance_type['id'] - del filters['flavor'] - # 'name' means Instance.display_name - # 'instance_name' means Instance.name - if 'name' in filters: - filters['display_name'] = filters['name'] - del filters['name'] - if 'instance_name' in filters: - filters['name'] = filters['instance_name'] - del filters['instance_name'] + def _remap_fixed_ip_filter(fixed_ip): + # Turn fixed_ip into a regexp match. Since '.' matches + # any character, we need to use regexp escaping for it. + filters['ip'] = '^%s$' % fixed_ip.replace('.', '\\.') + + # search_option to filter_name mapping. + filter_mapping = { + 'image': 'image_ref', + 'name': 'display_name', + 'instance_name': 'name', + 'recurse_zones': None, + 'flavor': _remap_flavor_filter, + 'fixed_ip': _remap_fixed_ip_filter} + + # copy from search_opts, doing various remappings as necessary + for opt, value in search_opts.iteritems(): + # Do remappings. + # Values not in the filter_mapping table are copied as-is. + # If remapping is None, option is not copied + # If the remapping is a string, it is the filter_name to use + try: + remap_object = filter_mapping[opt] + except KeyError: + filters[opt] = value + else: + if remap_object: + if isinstance(remap_object, basestring): + filters[remap_object] = value + else: + remap_object(value) + + recurse_zones = search_opts.get('recurse_zones', False) if 'reservation_id' in filters: recurse_zones = True - if 'fixed_ip' in search_opts: - # special cased for ec2. we end up ignoring all other - # search options. - try: - instance = self.db.instance_get_by_fixed_ip(context, - search_opts['fixed_ip']) - except exception.FloatingIpNotFound, e: - if not recurse_zones: - raise - if instance: - return [instance] - instances = [] - # fall through - else: - instances = self.db.instance_get_all_by_filters(context, filters) + instances = self.db.instance_get_all_by_filters(context, filters) if not recurse_zones: return instances @@ -743,12 +748,6 @@ class API(base.Base): server._info['_is_precooked'] = True instances.append(server._info) - # fixed_ip searching should return a FixedIpNotFound exception - # when an instance is not found... - fixed_ip = search_opts.get('fixed_ip', None) - if fixed_ip and not instances: - raise exception.FixedIpNotFoundForAddress(address=fixed_ip) - return instances def _cast_compute_message(self, method, context, instance_id, host=None, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 7792f5909..18ec08597 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -949,23 +949,32 @@ class ComputeTestCase(test.TestCase): instance_id2 = self._create_instance({'id': 20}) instance_id3 = self._create_instance({'id': 30}) + vif_ref1 = db.virtual_interface_create(c, + {'address': '12:34:56:78:90:12', + 'instance_id': instance_id1, + 'network_id': 1}) + vif_ref2 = db.virtual_interface_create(c, + {'address': '90:12:34:56:78:90', + 'instance_id': instance_id2, + 'network_id': 1}) + db.fixed_ip_create(c, {'address': '1.1.1.1', - 'instance_id': instance_id1}) + 'instance_id': instance_id1, + 'virtual_interface_id': vif_ref1['id']}) db.fixed_ip_create(c, {'address': '1.1.2.1', - 'instance_id': instance_id2}) + 'instance_id': instance_id2, + 'virtual_interface_id': vif_ref2['id']}) # regex not allowed - self.assertRaises(exception.NotFound, - self.compute_api.get_all, - c, + instances = self.compute_api.get_all(c, search_opts={'fixed_ip': '.*'}) + self.assertEqual(len(instances), 0) - self.assertRaises(exception.NotFound, - self.compute_api.get_all, - c, + instances = self.compute_api.get_all(c, search_opts={'fixed_ip': '1.1.3.1'}) + self.assertEqual(len(instances), 0) instances = self.compute_api.get_all(c, search_opts={'fixed_ip': '1.1.1.1'}) @@ -977,6 +986,8 @@ class ComputeTestCase(test.TestCase): self.assertEqual(len(instances), 1) self.assertEqual(instances[0].id, instance_id2) + db.virtual_interface_delete(c, vif_ref1['id']) + db.virtual_interface_delete(c, vif_ref2['id']) db.instance_destroy(c, instance_id1) db.instance_destroy(c, instance_id2) -- cgit From b19dbcf21865aa0d1b422aecdb7ff13571ecb4e8 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 6 Jul 2011 06:37:28 -0700 Subject: fix metadata test since fixed_ip searching now goes thru filters db api call instead of the get_by_fixed_ip call --- nova/tests/test_metadata.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index b9b14d1ea..d63394ad6 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -43,16 +43,20 @@ class MetadataTestCase(test.TestCase): 'reservation_id': 'r-xxxxxxxx', 'user_data': '', 'image_ref': 7, + 'fixed_ips': [], 'hostname': 'test'}) def instance_get(*args, **kwargs): return self.instance + def instance_get_list(*args, **kwargs): + return [self.instance] + def floating_get(*args, **kwargs): return '99.99.99.99' self.stubs.Set(api, 'instance_get', instance_get) - self.stubs.Set(api, 'instance_get_by_fixed_ip', instance_get) + self.stubs.Set(api, 'instance_get_all_by_filters', instance_get_list) self.stubs.Set(api, 'instance_get_floating_address', floating_get) self.app = metadatarequesthandler.MetadataRequestHandler() -- cgit From 65fcbc8cf51cc02071d1d9cd60cf0eb59c2bcce0 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 6 Jul 2011 06:44:50 -0700 Subject: merge code i'd split from instance_get_fixed_addresses_v6 that's no longer needed to be split --- nova/db/sqlalchemy/api.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 65a1c19a1..503c526f0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1395,33 +1395,29 @@ def instance_get_fixed_addresses(context, instance_id): return [fixed_ip.address for fixed_ip in fixed_ips] -def _ipv6_get_by_instance_ref(context, instance_ref): - # assume instance has 1 mac for each network associated with it - # get networks associated with instance - network_refs = network_get_all_by_instance(context, instance_id) - # compile a list of cidr_v6 prefixes sorted by network id - prefixes = [ref.cidr_v6 for ref in - sorted(network_refs, key=lambda ref: ref.id)] - # get vifs associated with instance - vif_refs = virtual_interface_get_by_instance(context, instance_ref.id) - # compile list of the mac_addresses for vifs sorted by network id - macs = [vif_ref['address'] for vif_ref in - sorted(vif_refs, key=lambda vif_ref: vif_ref['network_id'])] - # get project id from instance - project_id = instance_ref.project_id - # combine prefixes, macs, and project_id into (prefix,mac,p_id) tuples - prefix_mac_tuples = zip(prefixes, macs, [project_id for m in macs]) - # return list containing ipv6 address for each tuple - return [ipv6.to_global(*t) for t in prefix_mac_tuples] - - @require_context def instance_get_fixed_addresses_v6(context, instance_id): session = get_session() with session.begin(): # get instance instance_ref = instance_get(context, instance_id, session=session) - return _ipv6_get_by_instance_ref(context, instance_ref) + # assume instance has 1 mac for each network associated with it + # get networks associated with instance + network_refs = network_get_all_by_instance(context, instance_id) + # compile a list of cidr_v6 prefixes sorted by network id + prefixes = [ref.cidr_v6 for ref in + sorted(network_refs, key=lambda ref: ref.id)] + # get vifs associated with instance + vif_refs = virtual_interface_get_by_instance(context, instance_ref.id) + # compile list of the mac_addresses for vifs sorted by network id + macs = [vif_ref['address'] for vif_ref in + sorted(vif_refs, key=lambda vif_ref: vif_ref['network_id'])] + # get project id from instance + project_id = instance_ref.project_id + # combine prefixes, macs, and project_id into (prefix,mac,p_id) tuples + prefix_mac_tuples = zip(prefixes, macs, [project_id for m in macs]) + # return list containing ipv6 address for each tuple + return [ipv6.to_global(*t) for t in prefix_mac_tuples] @require_context -- cgit From ace9aa5d91d839f66998c39a977857b7a7c466a4 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 6 Jul 2011 08:25:28 -0700 Subject: wrap list comparison in test with set()s --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2f466f561..cfb1f9382 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1170,8 +1170,8 @@ class ServersTest(test.TestCase): def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) self.assertTrue('state' in search_opts) - self.assertEqual(search_opts['state'], - [power_state.RUNNING, power_state.BLOCKED]) + self.assertEqual(set(search_opts['state']), + set([power_state.RUNNING, power_state.BLOCKED])) return [stub_instance(100)] self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) -- cgit From 718d4cf5cd4122bcecf0974c441d098f57a124b0 Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sun, 17 Jul 2011 22:49:22 +0100 Subject: Initial test case proving we have a bug of, ec2 security group name can exceed 255 chars. --- nova/tests/test_api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 20b20fcbf..63f040ffd 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -293,6 +293,26 @@ class ApiEc2TestCase(test.TestCase): self.manager.delete_project(project) self.manager.delete_user(user) + def test_group_name_valid_security_group(self): + """Test that we sanely handle invalid security group names. """ + self.expect_http() + self.mox.ReplayAll() + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) + project = self.manager.create_project('fake', 'fake', 'fake') + + # At the moment, you need both of these to actually be netadmin + self.manager.add_role('fake', 'netadmin') + project.add_role('fake', 'netadmin') + + security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") + for x in range(random.randint(256, 266))) + try: + self.ec2.create_security_group(security_group_name, 'test group') + except: + pass + else: + self.fail('Exception not raised.') + def test_authorize_revoke_security_group_cidr(self): """ Test that we can add and remove CIDR based rules -- 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 +++++++++++++++++ nova/exception.py | 4 ++++ 3 files changed, 25 insertions(+) (limited to 'nova') 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): diff --git a/nova/exception.py b/nova/exception.py index ad6c005f8..8771328d8 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -196,6 +196,10 @@ class InvalidIpProtocol(Invalid): class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") +class InvalidParameterValue(Invalid): + # Cannot be templated as the error syntax varies. + # msg needs to be constructed when raised. + message = _("%(err)s") class InstanceNotRunning(Invalid): message = _("Instance %(instance_id)s is not running.") -- cgit From 64a03d48bd714672a3d68136d365bf941201affa Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Mon, 18 Jul 2011 00:06:48 +0100 Subject: Extended test to check for error specific error code and test cover for bad chars. --- nova/tests/test_api.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 63f040ffd..de399d76e 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -304,12 +304,32 @@ class ApiEc2TestCase(test.TestCase): self.manager.add_role('fake', 'netadmin') project.add_role('fake', 'netadmin') + # Test block group_name of non alphanumeric characters, spaces, + # dashes, and underscores. + security_group_name = "aa #$% -=99" + + try: + self.ec2.create_security_group(security_group_name, 'test group') + except EC2ResponseError, e: + if e.code == 'InvalidParameterValue': + pass + else: + self.fail("Unexpected EC2ResponseError: %s " + "(expected InvalidParameterValue)" % e.code) + else: + self.fail('Exception not raised.') + + # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) try: self.ec2.create_security_group(security_group_name, 'test group') - except: - pass + except EC2ResponseError, e: + if e.code == 'InvalidParameterValue': + pass + else: + self.fail("Unexpected EC2ResponseError: %s " + "(expected InvalidParameterValue)" % e.code) else: self.fail('Exception not raised.') -- 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 +++--- nova/exception.py | 6 ++++-- nova/tests/test_api.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'nova') 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 diff --git a/nova/exception.py b/nova/exception.py index 8771328d8..8f3cf0af6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -196,11 +196,13 @@ class InvalidIpProtocol(Invalid): class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") + +# Cannot be templated as the error syntax varies. +# msg needs to be constructed when raised. class InvalidParameterValue(Invalid): - # Cannot be templated as the error syntax varies. - # msg needs to be constructed when raised. message = _("%(err)s") + class InstanceNotRunning(Invalid): message = _("Instance %(instance_id)s is not running.") diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index de399d76e..6e4f2c95e 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -304,8 +304,8 @@ class ApiEc2TestCase(test.TestCase): self.manager.add_role('fake', 'netadmin') project.add_role('fake', 'netadmin') - # Test block group_name of non alphanumeric characters, spaces, - # dashes, and underscores. + # Test block group_name of non alphanumeric characters, spaces, + # dashes, and underscores. security_group_name = "aa #$% -=99" try: -- 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') 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') 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 5e9e62c2382f29a55b9b0c7a2b4aefc16b9d623d Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Wed, 20 Jul 2011 20:11:47 +0100 Subject: Split tests into 2 --- nova/tests/test_api.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 48a43a46b..5759e7726 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -363,8 +363,10 @@ class ApiEc2TestCase(test.TestCase): self.manager.delete_project(project) self.manager.delete_user(user) - def test_group_name_valid_security_group(self): - """Test that we sanely handle invalid security group names. """ + def test_group_name_valid_chars_security_group(self): + """ Test that we sanely handle invalid security group names. + API Spec states we should only accept alphanumeric characters, + spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() user = self.manager.create_user('fake', 'fake', 'fake', admin=True) @@ -376,7 +378,7 @@ class ApiEc2TestCase(test.TestCase): # Test block group_name of non alphanumeric characters, spaces, # dashes, and underscores. - security_group_name = "aa #$% -=99" + security_group_name = "aa #^% -=99" try: self.ec2.create_security_group(security_group_name, 'test group') @@ -389,6 +391,18 @@ class ApiEc2TestCase(test.TestCase): else: self.fail('Exception not raised.') + def test_group_name_valid_length_security_group(self): + """Test that we sanely handle invalid security group names. + API Spec states that the length should not exceed 255 chars """ + self.expect_http() + self.mox.ReplayAll() + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) + project = self.manager.create_project('fake', 'fake', 'fake') + + # At the moment, you need both of these to actually be netadmin + self.manager.add_role('fake', 'netadmin') + project.add_role('fake', 'netadmin') + # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) -- 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') 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 ------------------- nova/block_device.py | 35 +++++++++++++++++++++++++++++++++++ nova/compute/api.py | 3 ++- nova/tests/test_api.py | 7 +++++-- 5 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 nova/block_device.py (limited to 'nova') 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: diff --git a/nova/block_device.py b/nova/block_device.py new file mode 100644 index 000000000..963dffa37 --- /dev/null +++ b/nova/block_device.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# 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. + + +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 diff --git a/nova/compute/api.py b/nova/compute/api.py index 9994e5724..43a95aa17 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -22,6 +22,7 @@ import eventlet import re import time +from nova import block_device from nova import db from nova import exception from nova import flags @@ -218,7 +219,7 @@ class API(base.Base): if reservation_id is None: reservation_id = utils.generate_uid('r') - root_device_name = ec2utils.properties_root_device_name( + root_device_name = block_device.properties_root_device_name( image['properties']) base_options = { diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 26ac5ff24..d5f653bc6 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -27,6 +27,7 @@ import random import StringIO import webob +from nova import block_device from nova import context from nova import exception from nova import test @@ -147,10 +148,12 @@ class Ec2utilsTestCase(test.TestCase): properties0 = {'mappings': mappings} properties1 = {'root_device_name': '/dev/sdb', 'mappings': mappings} - root_device_name = ec2utils.properties_root_device_name(properties0) + root_device_name = block_device.properties_root_device_name( + properties0) self.assertEqual(root_device_name, '/dev/sda1') - root_device_name = ec2utils.properties_root_device_name(properties1) + root_device_name = block_device.properties_root_device_name( + properties1) self.assertEqual(root_device_name, '/dev/sdb') def test_mapping_prepend_dev(self): -- 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 ---------- nova/block_device.py | 36 ++++++++++++++++++++++++++++++++++++ nova/tests/test_api.py | 2 +- 4 files changed, 40 insertions(+), 16 deletions(-) (limited to 'nova') 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 diff --git a/nova/block_device.py b/nova/block_device.py index 963dffa37..8d95e0029 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import re + def properties_root_device_name(properties): """get root device name from image meta data. @@ -33,3 +35,37 @@ def properties_root_device_name(properties): root_device_name = properties['root_device_name'] return root_device_name + + +_ephemeral = re.compile('^ephemeral(\d|[1-9]\d+)$') + + +def is_ephemeral(device_name): + return _ephemeral.match(device_name) + + +def ephemeral_num(ephemeral_name): + assert is_ephemeral(ephemeral_name) + return int(_ephemeral.sub('\\1', ephemeral_name)) + + +def is_swap_or_ephemeral(device_name): + return device_name == 'swap' or is_ephemeral(device_name) + + +def mappings_prepend_dev(mappings): + """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" + for m in mappings: + virtual = m['virtual'] + if (is_swap_or_ephemeral(virtual) and + (not m['device'].startswith('/'))): + m['device'] = '/dev/' + m['device'] + return mappings + + +_dev = re.compile('^/dev/') + + +def strip_dev(device_name): + """remove leading '/dev/'""" + return _dev.sub('', device_name) diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index d5f653bc6..e3d2ee2fc 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -187,7 +187,7 @@ class Ec2utilsTestCase(test.TestCase): 'device': '/dev/sdc1'}, {'virtual': 'ephemeral1', 'device': '/dev/sdc1'}] - self.assertDictListMatch(ec2utils.mappings_prepend_dev(mappings), + self.assertDictListMatch(block_device.mappings_prepend_dev(mappings), expected_result) -- cgit From 9be2793c2e057a5e4f8c8c4dd2131ddcc3b11608 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: db/api: block_device_mapping_update_or_create() It is possible to have same virtual device name. So eliminate old entries whose entry has same virtual device name. --- nova/db/sqlalchemy/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ba03cabbc..ad51f5192 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -20,6 +20,7 @@ Implementation of SQLAlchemy backend. """ import warnings +from nova import block_device from nova import db from nova import exception from nova import flags @@ -2264,6 +2265,20 @@ def block_device_mapping_update_or_create(context, values): else: result.update(values) + # NOTE(yamahata): same virtual device name can be specified multiple + # times. So delete the existing ones. + virtual_name = values['virtual_name'] + if (virtual_name is not None and + block_device.is_swap_or_ephemeral(virtual_name)): + session.query(models.BlockDeviceMapping).\ + filter_by(instance_id=values['instance_id']).\ + filter_by(virtual_name=virtual_name).\ + filter(models.BlockDeviceMapping.device_name != + values['device_name']).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + @require_context def block_device_mapping_get_all_by_instance(context, instance_id): -- 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') 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') 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 e0517aef19bb00aa88809cb3c7d650ea38a08be2 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: compute/manager, virt: pass down root device name/swap/ephemeral to virt driver This patch makes compute/manager pass down infos about root device name, swap and ephemerals to virt driver. --- nova/compute/manager.py | 33 +++++++++++++++++++++++++-------- nova/virt/driver.py | 25 ++++++++++++++++++++++++- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/libvirt/connection.py | 25 ++++++++++++++----------- nova/virt/xenapi_conn.py | 2 +- 6 files changed, 66 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5819a520a..a4d2797d6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -44,6 +44,7 @@ import functools from eventlet import greenthread +from nova import block_device from nova import exception from nova import flags import nova.image @@ -223,6 +224,8 @@ class ComputeManager(manager.SchedulerDependentManager): volume_api = volume.API() block_device_mapping = [] + swap = None + ephemerals = [] for bdm in self.db.block_device_mapping_get_all_by_instance( context, instance_id): LOG.debug(_("setting up bdm %s"), bdm) @@ -230,11 +233,18 @@ class ComputeManager(manager.SchedulerDependentManager): if bdm['no_device']: continue if bdm['virtual_name']: - # TODO(yamahata): - # block devices for swap and ephemeralN will be - # created by virt driver locally in compute node. - assert (bdm['virtual_name'] == 'swap' or - bdm['virtual_name'].startswith('ephemeral')) + virtual_name = bdm['virtual_name'] + device_name = bdm['device_name'] + assert block_device.is_swap_or_ephemeral(virtual_name) + if virtual_name == 'swap': + swap = {'device_name': device_name, + 'swap_size': bdm['volume_size']} + elif block_device.is_ephemeral(virtual_name): + eph = {'num': block_device.ephemeral_num(virtual_name), + 'virtual_name': virtual_name, + 'device_name': device_name, + 'size': bdm['volume_size']} + ephemerals.append(eph) continue if ((bdm['snapshot_id'] is not None) and @@ -270,7 +280,7 @@ class ComputeManager(manager.SchedulerDependentManager): 'mount_device': bdm['device_name']}) - return block_device_mapping + return (swap, ephemerals, block_device_mapping) def _run_instance(self, context, instance_id, **kwargs): """Launch a new instance with specified options.""" @@ -313,13 +323,20 @@ class ComputeManager(manager.SchedulerDependentManager): # all vif creation and network injection, maybe this is correct network_info = [] - bd_mapping = self._setup_block_device_mapping(context, instance_id) + (swap, ephemerals, + block_device_mapping) = self._setup_block_device_mapping( + context, instance_id) + block_device_info = { + 'root_device_name': instance['root_device_name'], + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} # TODO(vish) check to make sure the availability zone matches self._update_state(context, instance_id, power_state.BUILDING) try: - self.driver.spawn(instance, network_info, bd_mapping) + self.driver.spawn(instance, network_info, block_device_info) except Exception as ex: # pylint: disable=W0702 msg = _("Instance '%(instance_id)s' failed to spawn. Is " "virtualization enabled in the BIOS? Details: " diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 178279d31..62c4f7ead 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -32,6 +32,29 @@ class InstanceInfo(object): self.state = state +def block_device_info_get_root(block_device_info): + block_device_info = block_device_info or {} + return block_device_info.get('root_device_name') + + +def block_device_info_get_swap(block_device_info): + block_device_info = block_device_info or {} + return block_device_info.get('swap') or {'device_name': None, + 'swap_size': 0} + + +def block_device_info_get_ephemerals(block_device_info): + block_device_info = block_device_info or {} + ephemerals = block_device_info.get('ephemerals') or [] + return ephemerals + + +def block_device_info_get_mapping(block_device_info): + block_device_info = block_device_info or {} + block_device_mapping = block_device_info.get('block_device_mapping') or [] + return block_device_mapping + + class ComputeDriver(object): """Base class for compute drivers. @@ -61,7 +84,7 @@ class ComputeDriver(object): """Return a list of InstanceInfo for all registered VMs""" raise NotImplementedError() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """Launch a VM for the specified instance""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..48a03dac8 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -129,7 +129,7 @@ class FakeConnection(driver.ComputeDriver): info_list.append(self._map_to_instance_info(instance)) return info_list - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """ Create a new instance/VM/domain on the virtualization platform. diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..f0ce71392 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -139,7 +139,7 @@ class HyperVConnection(driver.ComputeDriver): return instance_infos - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """ Create a new VM and start it.""" vm = self._lookup(instance.name) if vm is not None: diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..264c88a9e 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -580,14 +580,13 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): xml = self.to_xml(instance, False, network_info=network_info, - block_device_mapping=block_device_mapping) - block_device_mapping = block_device_mapping or [] + block_device_info=block_device_info) self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info=network_info, - block_device_mapping=block_device_mapping) + block_device_info=block_device_info) domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) @@ -769,8 +768,12 @@ class LibvirtConnection(driver.ComputeDriver): # TODO(vish): should we format disk by default? def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None, - network_info=None, block_device_mapping=None): - block_device_mapping = block_device_mapping or [] + network_info=None, block_device_info=None): + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) + + if not network_info: + network_info = netutils.get_network_info(inst) if not suffix: suffix = '' @@ -974,8 +977,9 @@ class LibvirtConnection(driver.ComputeDriver): return False def _prepare_xml_info(self, instance, rescue=False, network_info=None, - block_device_mapping=None): - block_device_mapping = block_device_mapping or [] + block_device_info=None): + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) # TODO(adiantum) remove network_info creation code # when multinics will be completed if not network_info: @@ -1030,12 +1034,11 @@ class LibvirtConnection(driver.ComputeDriver): return xml_info def to_xml(self, instance, rescue=False, network_info=None, - block_device_mapping=None): - block_device_mapping = block_device_mapping or [] + block_device_info=None): # TODO(termie): cache? LOG.debug(_('instance %s: starting toXML method'), instance['name']) xml_info = self._prepare_xml_info(instance, rescue, network_info, - block_device_mapping) + block_device_info) xml = str(Template(self.libvirt_xml, searchList=[xml_info])) LOG.debug(_('instance %s: finished toXML method'), instance['name']) return xml diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..4c6f9fe46 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -194,7 +194,7 @@ class XenAPIConnection(driver.ComputeDriver): def list_instances_detail(self): return self._vmops.list_instances_detail() - def spawn(self, instance, network_info, block_device_mapping=None): + def spawn(self, instance, network_info=None, block_device_info=None): """Create VM instance""" self._vmops.spawn(instance, network_info) -- cgit From e05b3b11e67f18a6ff4867dfbc75554fd78cad1b Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:55:25 +0900 Subject: compute/api: pass down ephemeral device info --- nova/compute/api.py | 70 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 43a95aa17..942114161 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -33,7 +33,6 @@ from nova import quota from nova import rpc from nova import utils from nova import volume -from nova.api.ec2 import ec2utils from nova.compute import instance_types from nova.compute import power_state from nova.compute.utils import terminate_volumes @@ -251,34 +250,64 @@ class API(base.Base): return (num_instances, base_options, image) - def _update_image_block_device_mapping(self, elevated_context, instance_id, + @staticmethod + def _ephemeral_size(instance_type, ephemeral_name): + num = block_device.ephemeral_num(ephemeral_name) + + # TODO(yamahata): ephemeralN where N > 0 + # Only ephemeral0 is allowed for now because InstanceTypes + # table only allows single local disk, local_gb. + # In order to enhance it, we need to add a new columns to + # instance_types table. + if num > 0: + return 0 + + return instance_type.get('local_gb') + + def _update_image_block_device_mapping(self, elevated_context, + instance_type, instance_id, mappings): """tell vm driver to create ephemeral/swap device at boot time by updating BlockDeviceMapping """ - for bdm in ec2utils.mappings_prepend_dev(mappings): + instance_type = (instance_type or + instance_types.get_default_instance_type()) + + for bdm in block_device.mappings_prepend_dev(mappings): LOG.debug(_("bdm %s"), bdm) virtual_name = bdm['virtual'] if virtual_name == 'ami' or virtual_name == 'root': continue - assert (virtual_name == 'swap' or - virtual_name.startswith('ephemeral')) + if not block_device.is_swap_or_ephemeral(virtual_name): + continue + + size = 0 + if virtual_name == 'swap': + size = instance_type.get('swap', 0) + elif block_device.is_ephemeral(virtual_name): + size = self._ephemeral_size(instance_type, virtual_name) + + if size == 0: + continue + values = { 'instance_id': instance_id, 'device_name': bdm['device'], - 'virtual_name': virtual_name, } + 'virtual_name': virtual_name, + 'volume_size': size} self.db.block_device_mapping_update_or_create(elevated_context, values) - def _update_block_device_mapping(self, elevated_context, instance_id, + def _update_block_device_mapping(self, elevated_context, + instance_type, instance_id, block_device_mapping): """tell vm driver to attach volume at boot time by updating BlockDeviceMapping """ + LOG.debug(_("block_device_mapping %s"), block_device_mapping) for bdm in block_device_mapping: - LOG.debug(_('bdm %s'), bdm) assert 'device_name' in bdm values = {'instance_id': instance_id} @@ -287,10 +316,18 @@ class API(base.Base): 'no_device'): values[key] = bdm.get(key) + virtual_name = bdm.get('virtual_name') + if (virtual_name is not None and + block_device.is_ephemeral(virtual_name)): + size = self._ephemeral_size(instance_type, virtual_name) + if size == 0: + continue + values['volume_size'] = size + # NOTE(yamahata): NoDevice eliminates devices defined in image # files by command line option. # (--block-device-mapping) - if bdm.get('virtual_name') == 'NoDevice': + if virtual_name == 'NoDevice': values['no_device'] = True for k in ('delete_on_termination', 'volume_id', 'snapshot_id', 'volume_id', 'volume_size', @@ -300,8 +337,8 @@ class API(base.Base): self.db.block_device_mapping_update_or_create(elevated_context, values) - def create_db_entry_for_new_instance(self, context, image, base_options, - security_group, block_device_mapping, num=1): + def create_db_entry_for_new_instance(self, context, instance_type, image, + base_options, security_group, block_device_mapping, num=1): """Create an entry in the DB for this new instance, including any related table updates (such as security group, etc). @@ -334,12 +371,12 @@ class API(base.Base): security_group_id) # BlockDeviceMapping table - self._update_image_block_device_mapping(elevated, instance_id, - image['properties'].get('mappings', [])) - self._update_block_device_mapping(elevated, instance_id, + self._update_image_block_device_mapping(elevated, instance_type, + instance_id, image['properties'].get('mappings', [])) + self._update_block_device_mapping(elevated, instance_type, instance_id, image['properties'].get('block_device_mapping', [])) # override via command line option - self._update_block_device_mapping(elevated, instance_id, + self._update_block_device_mapping(elevated, instance_type, instance_id, block_device_mapping) # Set sane defaults if not specified @@ -454,7 +491,8 @@ class API(base.Base): instances = [] LOG.debug(_("Going to run %s instances..."), num_instances) for num in range(num_instances): - instance = self.create_db_entry_for_new_instance(context, image, + instance = self.create_db_entry_for_new_instance(context, + instance_type, image, base_options, security_group, block_device_mapping, num=num) instances.append(instance) -- cgit From 3c8cc5b06f477b88d20a748a924d6afac5c5260f Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:01 +0900 Subject: virt/libvirt: teach libvirt driver root device name This patch teaches libvirt driver root device name. --- nova/virt/libvirt.xml.template | 11 +++++++---- nova/virt/libvirt/connection.py | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e1a683da8..e46a75915 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -12,13 +12,15 @@ #set $disk_bus = 'uml' uml /usr/bin/linux - /dev/ubda + #set $root_device_name = $getVar('root_device_name', '/dev/ubda') + ${root_device_name} #else #if $type == 'xen' #set $disk_prefix = 'sd' #set $disk_bus = 'scsi' linux - /dev/xvda + #set $root_device_name = $getVar('root_device_name', '/dev/xvda') + ${root_device_name} #else #set $disk_prefix = 'vd' #set $disk_bus = 'virtio' @@ -33,7 +35,8 @@ #if $type == 'xen' ro #else - root=/dev/vda console=ttyS0 + #set $root_device_name = $getVar('root_device_name', '/dev/vda') + root=${root_device_name} console=ttyS0 #end if #if $getVar('ramdisk', None) ${ramdisk} @@ -71,7 +74,7 @@ - + #end if #if $getVar('local', False) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 264c88a9e..30ad3c4fb 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -54,6 +54,7 @@ from xml.etree import ElementTree from eventlet import greenthread from eventlet import tpool +from nova import block_device from nova import context from nova import db from nova import exception @@ -834,7 +835,7 @@ class LibvirtConnection(driver.ComputeDriver): size = None root_fname += "_sm" - if not self._volume_in_mapping(self.root_mount_device, + if not self._volume_in_mapping(self.default_root_device, block_device_mapping): self._cache_image(fn=self._fetch_image, target=basepath('disk'), @@ -965,13 +966,13 @@ class LibvirtConnection(driver.ComputeDriver): return result - root_mount_device = 'vda' # FIXME for now. it's hard coded. + default_root_device = 'vda' # FIXME for now. it's hard coded. local_mount_device = 'vdb' # FIXME for now. it's hard coded. def _volume_in_mapping(self, mount_device, block_device_mapping): - mount_device_ = _strip_dev(mount_device) + mount_device_ = block_device.strip_dev(mount_device) for vol in block_device_mapping: - vol_mount_device = _strip_dev(vol['mount_device']) + vol_mount_device = block_device.strip_dev(vol['mount_device']) if vol_mount_device == mount_device_: return True return False @@ -998,8 +999,8 @@ class LibvirtConnection(driver.ComputeDriver): driver_type = 'raw' for vol in block_device_mapping: - vol['mount_device'] = _strip_dev(vol['mount_device']) - ebs_root = self._volume_in_mapping(self.root_mount_device, + vol['mount_device'] = block_device.strip_dev(vol['mount_device']) + ebs_root = self._volume_in_mapping(self.default_root_device, block_device_mapping) if self._volume_in_mapping(self.local_mount_device, block_device_mapping): @@ -1020,6 +1021,11 @@ class LibvirtConnection(driver.ComputeDriver): 'ebs_root': ebs_root, 'volumes': block_device_mapping} + root_device_name = driver.block_device_info_get_root(block_device_info) + if root_device_name: + xml_info['root_device'] = block_device.strip_dev(root_device_name) + xml_info['root_device_name'] = root_device_name + if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host xml_info['vnc_keymap'] = FLAGS.vnc_keymap -- cgit From 2c1b9ac98673c0ef1ae931c6b9d84e4b0741eed9 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:03 +0900 Subject: virt/libvirt: teach libvirt driver swap/ephemeral device This patch teaches libvirt virt driver swap/ephemeral device. --- nova/virt/driver.py | 4 ++ nova/virt/libvirt.xml.template | 22 +++++-- nova/virt/libvirt/connection.py | 127 ++++++++++++++++++++++++++++++++-------- 3 files changed, 122 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 62c4f7ead..b2406e306 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -43,6 +43,10 @@ def block_device_info_get_swap(block_device_info): 'swap_size': 0} +def swap_is_usable(swap): + return swap and swap['device_name'] and swap['swap_size'] > 0 + + def block_device_info_get_ephemerals(block_device_info): block_device_info = block_device_info or {} ephemerals = block_device_info.get('ephemerals') or [] diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e46a75915..f7cf306bc 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -3,12 +3,10 @@ ${memory_kb} #if $type == 'lxc' - #set $disk_prefix = '' #set $disk_bus = '' exe /sbin/init #else if $type == 'uml' - #set $disk_prefix = 'ubd' #set $disk_bus = 'uml' uml /usr/bin/linux @@ -16,13 +14,11 @@ ${root_device_name} #else #if $type == 'xen' - #set $disk_prefix = 'sd' #set $disk_bus = 'scsi' linux #set $root_device_name = $getVar('root_device_name', '/dev/xvda') ${root_device_name} #else - #set $disk_prefix = 'vd' #set $disk_bus = 'virtio' hvm #end if @@ -77,13 +73,27 @@ #end if - #if $getVar('local', False) + #if $getVar('local_device', False) - + #end if + #for $eph in $ephemerals + + + + + + #end for + #if $getVar('swap_device', False) + + + + + + #end if #for $vol in $volumes diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 30ad3c4fb..cf013df30 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -149,8 +149,8 @@ def _late_load_cheetah(): Template = t.Template -def _strip_dev(mount_path): - return re.sub(r'^/dev/', '', mount_path) +def _get_eph_disk(ephemeral): + return 'disk.eph' + str(ephemeral['num']) class LibvirtConnection(driver.ComputeDriver): @@ -768,6 +768,11 @@ class LibvirtConnection(driver.ComputeDriver): utils.execute('truncate', target, '-s', "%dG" % local_gb) # TODO(vish): should we format disk by default? + def _create_swap(self, target, swap_gb): + """Create a swap file of specified size""" + self._create_local(target, swap_gb) + utils.execute('mkswap', target) + def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None, network_info=None, block_device_info=None): block_device_mapping = driver.block_device_info_get_mapping( @@ -836,7 +841,7 @@ class LibvirtConnection(driver.ComputeDriver): root_fname += "_sm" if not self._volume_in_mapping(self.default_root_device, - block_device_mapping): + block_device_info): self._cache_image(fn=self._fetch_image, target=basepath('disk'), fname=root_fname, @@ -846,13 +851,38 @@ class LibvirtConnection(driver.ComputeDriver): project=project, size=size) - if inst_type['local_gb'] and not self._volume_in_mapping( - self.local_mount_device, block_device_mapping): + local_gb = inst['local_gb'] + if local_gb and not self._volume_in_mapping( + self.default_local_device, block_device_info): self._cache_image(fn=self._create_local, target=basepath('disk.local'), - fname="local_%s" % inst_type['local_gb'], + fname="local_%s" % local_gb, + cow=FLAGS.use_cow_images, + local_gb=local_gb) + + for eph in driver.block_device_info_get_ephemerals(block_device_info): + self._cache_image(fn=self._create_local, + target=basepath(_get_eph_disk(eph)), + fname="local_%s" % eph['size'], + cow=FLAGS.use_cow_images, + local_gb=eph['size']) + + swap_gb = 0 + + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + swap_gb = swap['swap_size'] + elif (inst_type['swap'] > 0 and + not self._volume_in_mapping(self.default_swap_device, + block_device_info)): + swap_gb = inst_type['swap'] + + if swap_gb > 0: + self._cache_image(fn=self._create_swap, + target=basepath('disk.swap'), + fname="swap_%s" % swap_gb, cow=FLAGS.use_cow_images, - local_gb=inst_type['local_gb']) + swap_gb=swap_gb) # For now, we assume that if we're not using a kernel, we're using a # partitioned disk image where the target partition is the first @@ -966,16 +996,35 @@ class LibvirtConnection(driver.ComputeDriver): return result - default_root_device = 'vda' # FIXME for now. it's hard coded. - local_mount_device = 'vdb' # FIXME for now. it's hard coded. - - def _volume_in_mapping(self, mount_device, block_device_mapping): - mount_device_ = block_device.strip_dev(mount_device) - for vol in block_device_mapping: - vol_mount_device = block_device.strip_dev(vol['mount_device']) - if vol_mount_device == mount_device_: - return True - return False + if FLAGS.libvirt_type == 'uml': + _disk_prefix = 'ubd' + elif FLAGS.libvirt_type == 'xen': + _disk_prefix = 'sd' + elif FLAGS.libvirt_type == 'lxc': + _disk_prefix = '' + else: + _disk_prefix = 'vd' + + default_root_device = _disk_prefix + 'a' + default_local_device = _disk_prefix + 'b' + default_swap_device = _disk_prefix + 'c' + + def _volume_in_mapping(self, mount_device, block_device_info): + block_device_list = [block_device.strip_dev(vol['mount_device']) + for vol in + driver.block_device_info_get_mapping( + block_device_info)] + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + block_device_list.append( + block_device.strip_dev(swap['device_name'])) + block_device_list += [block_device.strip_dev(ephemeral['device_name']) + for ephemeral in + driver.block_device_info_get_ephemerals( + block_device_info)] + + LOG.debug(_("block_device_list %s"), block_device_list) + return block_device.strip_dev(mount_device) in block_device_list def _prepare_xml_info(self, instance, rescue=False, network_info=None, block_device_info=None): @@ -1000,13 +1049,24 @@ class LibvirtConnection(driver.ComputeDriver): for vol in block_device_mapping: vol['mount_device'] = block_device.strip_dev(vol['mount_device']) + ebs_root = self._volume_in_mapping(self.default_root_device, - block_device_mapping) - if self._volume_in_mapping(self.local_mount_device, - block_device_mapping): - local_gb = False - else: - local_gb = inst_type['local_gb'] + block_device_info) + + local_device = False + if not (self._volume_in_mapping(self.default_local_device, + block_device_info) or + 0 in [eph['num'] for eph in + driver.block_device_info_get_ephemerals( + block_device_info)]): + if instance['local_gb'] > 0: + local_device = self.default_local_device + + ephemerals = [] + for eph in driver.block_device_info_get_ephemerals(block_device_info): + ephemerals.append({'device_path': _get_eph_disk(eph), + 'device': block_device.strip_dev( + eph['device_name'])}) xml_info = {'type': FLAGS.libvirt_type, 'name': instance['name'], @@ -1015,16 +1075,33 @@ class LibvirtConnection(driver.ComputeDriver): 'memory_kb': inst_type['memory_mb'] * 1024, 'vcpus': inst_type['vcpus'], 'rescue': rescue, - 'local': local_gb, + 'disk_prefix': self._disk_prefix, 'driver_type': driver_type, 'nics': nics, 'ebs_root': ebs_root, - 'volumes': block_device_mapping} + 'local_device': local_device, + 'volumes': block_device_mapping, + 'ephemerals': ephemerals} root_device_name = driver.block_device_info_get_root(block_device_info) if root_device_name: xml_info['root_device'] = block_device.strip_dev(root_device_name) xml_info['root_device_name'] = root_device_name + else: + # NOTE(yamahata): + # for nova.api.ec2.cloud.CloudController.get_metadata() + xml_info['root_device'] = self.default_root_device + db.instance_update(context.get_admin_context(), instance['id'], + {'root_device_name': '/dev/' + self.default_root_device}) + + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + xml_info['swap_device'] = block_device.strip_dev( + swap['device_name']) + elif (inst_type['swap'] > 0 and + not self._volume_in_mapping(self.default_swap_device, + block_device_info)): + xml_info['swap_device'] = self.default_swap_device if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host -- cgit From af21767505b668c882734552115decdf8a798581 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:03 +0900 Subject: test_libvirt: fix up for local_gb --- nova/tests/test_libvirt.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6e2ec7ed6..2a21d0d32 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -187,6 +187,7 @@ class LibvirtConnTestCase(test.TestCase): 'project_id': 'fake', 'bridge': 'br101', 'image_ref': '123456', + 'local_gb': 20, 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): -- cgit From 47e7a21d74ebd06d994ad41088adb92d615aab0c Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: test_compute: make test_compute pass --- nova/tests/test_compute.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 5d59b628a..c5ce18495 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -887,15 +887,17 @@ class ComputeTestCase(test.TestCase): return bdm def test_update_block_device_mapping(self): + swap_size = 1 + instance_type = {'swap': swap_size} instance_id = self._create_instance() mappings = [ {'virtual': 'ami', 'device': 'sda1'}, {'virtual': 'root', 'device': '/dev/sda1'}, - {'virtual': 'swap', 'device': 'sdb1'}, - {'virtual': 'swap', 'device': 'sdb2'}, - {'virtual': 'swap', 'device': 'sdb3'}, {'virtual': 'swap', 'device': 'sdb4'}, + {'virtual': 'swap', 'device': 'sdb3'}, + {'virtual': 'swap', 'device': 'sdb2'}, + {'virtual': 'swap', 'device': 'sdb1'}, {'virtual': 'ephemeral0', 'device': 'sdc1'}, {'virtual': 'ephemeral1', 'device': 'sdc2'}, @@ -937,19 +939,21 @@ class ComputeTestCase(test.TestCase): 'no_device': True}] self.compute_api._update_image_block_device_mapping( - self.context, instance_id, mappings) + self.context, instance_type, instance_id, mappings) bdms = [self._parse_db_block_device_mapping(bdm_ref) for bdm_ref in db.block_device_mapping_get_all_by_instance( self.context, instance_id)] expected_result = [ - {'virtual_name': 'swap', 'device_name': '/dev/sdb1'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb2'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb3'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb4'}, + {'virtual_name': 'swap', 'device_name': '/dev/sdb1', + 'volume_size': swap_size}, {'virtual_name': 'ephemeral0', 'device_name': '/dev/sdc1'}, - {'virtual_name': 'ephemeral1', 'device_name': '/dev/sdc2'}, - {'virtual_name': 'ephemeral2', 'device_name': '/dev/sdc3'}] + + # NOTE(yamahata): ATM only ephemeral0 is supported. + # they're ignored for now + #{'virtual_name': 'ephemeral1', 'device_name': '/dev/sdc2'}, + #{'virtual_name': 'ephemeral2', 'device_name': '/dev/sdc3'} + ] bdms.sort() expected_result.sort() self.assertDictListMatch(bdms, expected_result) @@ -962,7 +966,8 @@ class ComputeTestCase(test.TestCase): expected_result = [ {'snapshot_id': 0x12345678, 'device_name': '/dev/sda1'}, - {'virtual_name': 'swap', 'device_name': '/dev/sdb1'}, + {'virtual_name': 'swap', 'device_name': '/dev/sdb1', + 'volume_size': swap_size}, {'snapshot_id': 0x23456789, 'device_name': '/dev/sdb2'}, {'snapshot_id': 0x3456789A, 'device_name': '/dev/sdb3'}, {'no_device': True, 'device_name': '/dev/sdb4'}, -- cgit From 51c0c36bc5357102d0fa564a73631f1420e253b1 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: test_metadata: make test_metadata pass --- nova/tests/test_metadata.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index c862726ab..f81e7a00a 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -43,6 +43,7 @@ class MetadataTestCase(test.TestCase): 'reservation_id': 'r-xxxxxxxx', 'user_data': '', 'image_ref': 7, + 'root_device_name': '/dev/sda1', 'hostname': 'test'}) def instance_get(*args, **kwargs): -- cgit From 77c34f0223a21d122062b2057e9ed1584dbbf8bf Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: nova/tests/test_compute.py: make test_compute.test_update_block_device_mapping happy --- nova/tests/test_compute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index c5ce18495..8f1364532 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -959,7 +959,8 @@ class ComputeTestCase(test.TestCase): self.assertDictListMatch(bdms, expected_result) self.compute_api._update_block_device_mapping( - self.context, instance_id, block_device_mapping) + self.context, instance_types.get_default_instance_type(), + instance_id, block_device_mapping) bdms = [self._parse_db_block_device_mapping(bdm_ref) for bdm_ref in db.block_device_mapping_get_all_by_instance( self.context, instance_id)] -- cgit From 4c1fd45270faef4b42504bb5e2b8bd3e49b14d8c Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: tests/test_cloud:test_modify_image: make it pass --- nova/tests/test_cloud.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8cdc73a66..0f1dfb813 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -852,13 +852,16 @@ class CloudTestCase(test.TestCase): def test_modify_image_attribute(self): modify_image_attribute = self.cloud.modify_image_attribute + fake_metadata = {'id': 1, 'container_format': 'ami', + 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': False} + def fake_show(meh, context, id): - return {'id': 1, 'container_format': 'ami', - 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type': 'machine'}, 'is_public': False} + return fake_metadata def fake_update(meh, context, image_id, metadata, data=None): - return metadata + fake_metadata.update(metadata) + return fake_metadata self.stubs.Set(fake._FakeImageService, 'show', fake_show) self.stubs.Set(fake._FakeImageService, 'show_by_name', fake_show) -- cgit From 405df88f00ce71621d3fda3ec52e5cf1217c8e05 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: image/glance: teach glance block device mapping --- nova/image/glance.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 5c2dc957b..88d3cf3af 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -19,7 +19,9 @@ from __future__ import absolute_import +import copy import datetime +import json import random from glance.common import exception as glance_exception @@ -184,6 +186,7 @@ class GlanceImageService(service.BaseImageService): """ # NOTE(vish): show is to check if image is available self.show(context, image_id) + image_meta = _convert_to_string(image_meta) try: image_meta = self.client.update_image(image_id, image_meta, data) except glance_exception.NotFound: @@ -210,12 +213,20 @@ class GlanceImageService(service.BaseImageService): """Clears out all images.""" pass + @classmethod + def _translate_to_service(cls, image_meta): + image_meta = super(GlanceImageService, + cls)._translate_to_service(image_meta) + image_meta = _convert_to_string(image_meta) + return image_meta + @classmethod def _translate_to_base(cls, image_meta): """Override translation to handle conversion to datetime objects.""" image_meta = service.BaseImageService._propertify_metadata( image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta = _convert_timestamps_to_datetimes(image_meta) + image_meta = _convert_from_string(image_meta) return image_meta @@ -241,3 +252,38 @@ def _parse_glance_iso8601_timestamp(timestamp): raise ValueError(_('%(timestamp)s does not follow any of the ' 'signatures: %(ISO_FORMATS)s') % locals()) + + +# TODO(yamahata): use block-device-mapping extension to glance +def _json_loads(properties, attr): + prop = properties[attr] + if isinstance(prop, basestring): + properties[attr] = json.loads(prop) + + +def _json_dumps(properties, attr): + prop = properties[attr] + if not isinstance(prop, basestring): + properties[attr] = json.dumps(prop) + + +_CONVERT_PROPS = ('block_device_mapping', 'mappings') + + +def _convert(method, metadata): + metadata = copy.deepcopy(metadata) # don't touch original metadata + properties = metadata.get('properties') + if properties: + for attr in _CONVERT_PROPS: + if attr in properties: + method(properties, attr) + + return metadata + + +def _convert_from_string(metadata): + return _convert(_json_loads, metadata) + + +def _convert_to_string(metadata): + return _convert(_json_dumps, metadata) -- cgit From 24b6597035c4393383ed1bdc2a6e52830743a7ea Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: db/api: fix network_get_by_cidr() User of the function is only 'nova-manage network delete'. It doesn't check deleted flag which must be checked. Otherwise some it might pick up deleted column depending on query result, and tries to delete already deleted columns and results in exception. --- nova/db/sqlalchemy/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ad51f5192..abfa6a3b7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1681,7 +1681,9 @@ def network_get_by_bridge(context, bridge): def network_get_by_cidr(context, cidr): session = get_session() result = session.query(models.Network).\ - filter_by(cidr=cidr).first() + filter_by(cidr=cidr).\ + filter_by(deleted=False).\ + first() if not result: raise exception.NetworkNotFoundForCidr(cidr=cidr) -- cgit From 4960b77202aba106adb8780ea724b26d958d5c81 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:04 +0900 Subject: tests: unit tests for nova.block_device --- nova/tests/test_block_device.py | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 nova/tests/test_block_device.py (limited to 'nova') diff --git a/nova/tests/test_block_device.py b/nova/tests/test_block_device.py new file mode 100644 index 000000000..b8e9b35e2 --- /dev/null +++ b/nova/tests/test_block_device.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# 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. + +""" +Tests for Block Device utility functions. +""" + +from nova import block_device +from nova import test + + +class BlockDeviceTestCase(test.TestCase): + def test_properties(self): + root_device0 = '/dev/sda' + root_device1 = '/dev/sdb' + mappings = [{'virtual': 'root', + 'device': root_device0}] + + properties0 = {'mappings': mappings} + properties1 = {'mappings': mappings, + 'root_device_name': root_device1} + + self.assertEqual(block_device.properties_root_device_name({}), None) + self.assertEqual( + block_device.properties_root_device_name(properties0), + root_device0) + self.assertEqual( + block_device.properties_root_device_name(properties1), + root_device1) + + def test_ephemeral(self): + self.assertFalse(block_device.is_ephemeral('ephemeral')) + self.assertTrue(block_device.is_ephemeral('ephemeral0')) + self.assertTrue(block_device.is_ephemeral('ephemeral1')) + self.assertTrue(block_device.is_ephemeral('ephemeral11')) + self.assertFalse(block_device.is_ephemeral('root')) + self.assertFalse(block_device.is_ephemeral('swap')) + self.assertFalse(block_device.is_ephemeral('/dev/sda1')) + + self.assertEqual(block_device.ephemeral_num('ephemeral0'), 0) + self.assertEqual(block_device.ephemeral_num('ephemeral1'), 1) + self.assertEqual(block_device.ephemeral_num('ephemeral11'), 11) + + self.assertFalse(block_device.is_swap_or_ephemeral('ephemeral')) + self.assertTrue(block_device.is_swap_or_ephemeral('ephemeral0')) + self.assertTrue(block_device.is_swap_or_ephemeral('ephemeral1')) + self.assertTrue(block_device.is_swap_or_ephemeral('swap')) + self.assertFalse(block_device.is_swap_or_ephemeral('root')) + self.assertFalse(block_device.is_swap_or_ephemeral('/dev/sda1')) + + def test_mappings_prepend_dev(self): + mapping = [ + {'virtual': 'ami', 'device': '/dev/sda'}, + {'virtual': 'root', 'device': 'sda'}, + {'virtual': 'ephemeral0', 'device': 'sdb'}, + {'virtual': 'swap', 'device': 'sdc'}, + {'virtual': 'ephemeral1', 'device': 'sdd'}, + {'virtual': 'ephemeral2', 'device': 'sde'}] + + expected = [ + {'virtual': 'ami', 'device': '/dev/sda'}, + {'virtual': 'root', 'device': 'sda'}, + {'virtual': 'ephemeral0', 'device': '/dev/sdb'}, + {'virtual': 'swap', 'device': '/dev/sdc'}, + {'virtual': 'ephemeral1', 'device': '/dev/sdd'}, + {'virtual': 'ephemeral2', 'device': '/dev/sde'}] + + prepended = block_device.mappings_prepend_dev(mapping) + self.assertEqual(prepended.sort(), expected.sort()) + + def test_strip_dev(self): + self.assertEqual(block_device.strip_dev('/dev/sda'), 'sda') + self.assertEqual(block_device.strip_dev('sda'), 'sda') -- cgit From 3af916ba0d87d383a89250b3aac4cf5e5b728f69 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for nova.virt --- nova/tests/test_virt.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 nova/tests/test_virt.py (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py new file mode 100644 index 000000000..388f075af --- /dev/null +++ b/nova/tests/test_virt.py @@ -0,0 +1,83 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Isaku Yamahata +# 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. + +from nova import flags +from nova import test +from nova.virt import driver + +FLAGS = flags.FLAGS + + +class TestVirtDriver(test.TestCase): + def test_block_device(self): + swap = {'device_name': '/dev/sdb', + 'swap_size': 1} + ephemerals = [{'num': 0, + 'virtual_name': 'ephemeral0', + 'device_name': '/dev/sdc1', + 'size': 1}] + block_device_mapping = [{'mount_device': '/dev/sde', + 'device_path': 'fake_device'}] + block_device_info = { + 'root_device_name': '/dev/sda', + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} + + empty_block_device_info = {} + + self.assertEqual( + driver.block_device_info_get_root(block_device_info), '/dev/sda') + self.assertEqual( + driver.block_device_info_get_root(empty_block_device_info), None) + self.assertEqual( + driver.block_device_info_get_root(None), None) + + self.assertEqual( + driver.block_device_info_get_swap(block_device_info), swap) + self.assertEqual(driver.block_device_info_get_swap( + empty_block_device_info)['device_name'], None) + self.assertEqual(driver.block_device_info_get_swap( + empty_block_device_info)['swap_size'], 0) + self.assertEqual( + driver.block_device_info_get_swap({'swap': None})['device_name'], + None) + self.assertEqual( + driver.block_device_info_get_swap({'swap': None})['swap_size'], + 0) + self.assertEqual( + driver.block_device_info_get_swap(None)['device_name'], None) + self.assertEqual( + driver.block_device_info_get_swap(None)['swap_size'], 0) + + self.assertEqual( + driver.block_device_info_get_ephemerals(block_device_info), + ephemerals) + self.assertEqual( + driver.block_device_info_get_ephemerals(empty_block_device_info), + []) + self.assertEqual( + driver.block_device_info_get_ephemerals(None), + []) + + def test_swap_is_usable(self): + self.assertFalse(driver.swap_is_usable(None)) + self.assertFalse(driver.swap_is_usable({'device_name': None})) + self.assertFalse(driver.swap_is_usable({'device_name': '/dev/sdb', + 'swap_size': 0})) + self.assertTrue(driver.swap_is_usable({'device_name': '/dev/sdb', + 'swap_size': 1})) -- cgit From ba6404f05d9fb34a729d45e1ee055c7a7156c5c4 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests/glance: unit tests for glance serializer --- nova/tests/image/test_glance.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 223e7ae57..488b03f9c 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -232,3 +232,39 @@ class TestMutatorDateTimeTests(BaseGlanceTest): 'updated_at': None, 'deleted_at': None} return fixture + + +class TestGlanceSerializer(unittest.TestCase): + def test_serialize(self): + metadata = {'name': 'image1', + 'is_public': True, + 'foo': 'bar', + 'properties': { + 'prop1': 'propvalue1', + 'mappings': [ + {'virtual': 'aaa', + 'device': 'bbb'}, + {'virtual': 'xxx', + 'device': 'yyy'}], + 'block_device_mapping': [ + {'virtual_device': 'fake', + 'device_name': '/dev/fake'}, + {'virtual_device': 'ephemeral0', + 'device_name': '/dev/fake0'}]}} + + converted_expected = { + 'name': 'image1', + 'is_public': True, + 'foo': 'bar', + 'properties': { + 'prop1': 'propvalue1', + 'mappings': + '[{"device": "bbb", "virtual": "aaa"}, ' + '{"device": "yyy", "virtual": "xxx"}]', + 'block_device_mapping': + '[{"virtual_device": "fake", "device_name": "/dev/fake"}, ' + '{"virtual_device": "ephemeral0", ' + '"device_name": "/dev/fake0"}]'}} + converted = glance._convert_to_string(metadata) + self.assertEqual(converted, converted_expected) + self.assertEqual(glance._convert_from_string(converted), metadata) -- cgit From 5113f78ddb8d7ccecea4e4ec8cbf35765af46d40 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for nova.virt.libvirt.connection._volume_in_mapping() --- nova/tests/test_libvirt.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 2a21d0d32..0c198f1b5 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -786,6 +786,42 @@ class LibvirtConnTestCase(test.TestCase): ip = conn.get_host_ip_addr() self.assertEquals(ip, FLAGS.my_ip) + def test_volume_in_mapping(self): + conn = connection.LibvirtConnection(False) + swap = {'device_name': '/dev/sdb', + 'swap_size': 1} + ephemerals = [{'num': 0, + 'virtual_name': 'ephemeral0', + 'device_name': '/dev/sdc1', + 'size': 1}, + {'num': 2, + 'virtual_name': 'ephemeral2', + 'device_name': '/dev/sdd', + 'size': 1}] + block_device_mapping = [{'mount_device': '/dev/sde', + 'device_path': 'fake_device'}, + {'mount_device': '/dev/sdf', + 'device_path': 'fake_device'}] + block_device_info = { + 'root_device_name': '/dev/sda', + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} + + def _assert_volume_in_mapping(device_name, true_or_false): + self.assertEquals(conn._volume_in_mapping(device_name, + block_device_info), + true_or_false) + + _assert_volume_in_mapping('sda', False) + _assert_volume_in_mapping('sdb', True) + _assert_volume_in_mapping('sdc1', True) + _assert_volume_in_mapping('sdd', True) + _assert_volume_in_mapping('sde', True) + _assert_volume_in_mapping('sdf', True) + _assert_volume_in_mapping('sdg', False) + _assert_volume_in_mapping('sdh1', False) + class NWFilterFakes: def __init__(self): -- cgit From 142a95a223a4259bcb3b35087b6d24f8310e3fa6 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: an unit test for nova.compute.api.API._ephemeral_size() --- nova/tests/test_compute.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 8f1364532..32f55c6e2 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -990,3 +990,13 @@ class ComputeTestCase(test.TestCase): self.context, instance_id): db.block_device_mapping_destroy(self.context, bdm['id']) self.compute.terminate_instance(self.context, instance_id) + + def test_ephemeral_size(self): + local_size = 2 + inst_type = {'local_gb': local_size} + self.assertEqual(self.compute_api._ephemeral_size(inst_type, + 'ephemeral0'), + local_size) + self.assertEqual(self.compute_api._ephemeral_size(inst_type, + 'ephemeral1'), + 0) -- cgit From 916231fd945c5e726a21decdf1b6370b2fcefe70 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 23 Jul 2011 16:57:05 +0900 Subject: tests: unit tests for describe instance attribute --- nova/tests/test_cloud.py | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 0f1dfb813..507b35d22 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -17,6 +17,8 @@ # under the License. import mox +import functools + from base64 import b64decode from M2Crypto import BIO from M2Crypto import RSA @@ -1438,3 +1440,145 @@ class CloudTestCase(test.TestCase): # TODO(yamahata): clean up snapshot created by CreateImage. self._restart_compute_service() + + @staticmethod + def _fake_bdm_get(ctxt, id): + return [{'volume_id': 87654321, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': None, + 'delete_on_termination': True, + 'device_name': '/dev/sdh'}, + {'volume_id': None, + 'snapshot_id': 98765432, + 'no_device': None, + 'virtual_name': None, + 'delete_on_termination': True, + 'device_name': '/dev/sdi'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': True, + 'virtual_name': None, + 'delete_on_termination': None, + 'device_name': None}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral0', + 'delete_on_termination': None, + 'device_name': '/dev/sdb'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'swap', + 'delete_on_termination': None, + 'device_name': '/dev/sdc'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral1', + 'delete_on_termination': None, + 'device_name': '/dev/sdd'}, + {'volume_id': None, + 'snapshot_id': None, + 'no_device': None, + 'virtual_name': 'ephemeral2', + 'delete_on_termination': None, + 'device_name': '/dev/sd3'}, + ] + + def test_get_instance_mapping(self): + """Make sure that _get_instance_mapping works""" + ctxt = None + instance_ref0 = {'id': 0, + 'root_device_name': None} + instance_ref1 = {'id': 0, + 'root_device_name': '/dev/sda1'} + + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', + self._fake_bdm_get) + + expected = {'ami': 'sda1', + 'root': '/dev/sda1', + 'ephemeral0': '/dev/sdb', + 'swap': '/dev/sdc', + 'ephemeral1': '/dev/sdd', + 'ephemeral2': '/dev/sd3'} + + self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref0), + cloud._DEFAULT_MAPPINGS) + self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref1), + expected) + + def test_describe_instance_attribute(self): + """Make sure that describe_instance_attribute works""" + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', + self._fake_bdm_get) + + def fake_get(ctxt, instance_id): + return { + 'id': 0, + 'root_device_name': '/dev/sdh', + 'security_groups': [{'name': 'fake0'}, {'name': 'fake1'}], + 'state_description': 'stopping', + 'instance_type': {'name': 'fake_type'}, + 'kernel_id': 1, + 'ramdisk_id': 2, + 'user_data': 'fake-user data', + } + self.stubs.Set(self.cloud.compute_api, 'get', fake_get) + + def fake_volume_get(ctxt, volume_id, session=None): + if volume_id == 87654321: + return {'id': volume_id, + 'attach_time': '13:56:24', + 'status': 'in-use'} + raise exception.VolumeNotFound(volume_id=volume_id) + self.stubs.Set(db.api, 'volume_get', fake_volume_get) + + get_attribute = functools.partial( + self.cloud.describe_instance_attribute, + self.context, 'i-12345678') + + bdm = get_attribute('blockDeviceMapping') + bdm['blockDeviceMapping'].sort() + + expected_bdm = {'instance_id': 'i-12345678', + 'rootDeviceType': 'ebs', + 'blockDeviceMapping': [ + {'deviceName': '/dev/sdh', + 'ebs': {'status': 'in-use', + 'deleteOnTermination': True, + 'volumeId': 87654321, + 'attachTime': '13:56:24'}}]} + expected_bdm['blockDeviceMapping'].sort() + self.assertEqual(bdm, expected_bdm) + # NOTE(yamahata): this isn't supported + # get_attribute('disableApiTermination') + groupSet = get_attribute('groupSet') + groupSet['groupSet'].sort() + expected_groupSet = {'instance_id': 'i-12345678', + 'groupSet': [{'groupId': 'fake0'}, + {'groupId': 'fake1'}]} + expected_groupSet['groupSet'].sort() + self.assertEqual(groupSet, expected_groupSet) + self.assertEqual(get_attribute('instanceInitiatedShutdownBehavior'), + {'instance_id': 'i-12345678', + 'instanceInitiatedShutdownBehavior': 'stop'}) + self.assertEqual(get_attribute('instanceType'), + {'instance_id': 'i-12345678', + 'instanceType': 'fake_type'}) + self.assertEqual(get_attribute('kernel'), + {'instance_id': 'i-12345678', + 'kernel': 'aki-00000001'}) + self.assertEqual(get_attribute('ramdisk'), + {'instance_id': 'i-12345678', + 'ramdisk': 'ari-00000002'}) + self.assertEqual(get_attribute('rootDeviceName'), + {'instance_id': 'i-12345678', + 'rootDeviceName': '/dev/sdh'}) + # NOTE(yamahata): this isn't supported + # get_attribute('sourceDestCheck') + self.assertEqual(get_attribute('userData'), + {'instance_id': 'i-12345678', + 'userData': '}\xa9\x1e\xba\xc7\xabu\xabZ'}) -- 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 +- nova/tests/test_api.py | 27 ++++++--------------------- 2 files changed, 7 insertions(+), 22 deletions(-) (limited to 'nova') 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, " diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 5759e7726..40e62ac76 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -365,7 +365,7 @@ class ApiEc2TestCase(test.TestCase): def test_group_name_valid_chars_security_group(self): """ Test that we sanely handle invalid security group names. - API Spec states we should only accept alphanumeric characters, + API Spec states we should only accept alphanumeric characters, spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() @@ -380,16 +380,8 @@ class ApiEc2TestCase(test.TestCase): # dashes, and underscores. security_group_name = "aa #^% -=99" - try: - self.ec2.create_security_group(security_group_name, 'test group') - except EC2ResponseError, e: - if e.code == 'InvalidParameterValue': - pass - else: - self.fail("Unexpected EC2ResponseError: %s " - "(expected InvalidParameterValue)" % e.code) - else: - self.fail('Exception not raised.') + self.assertRaises(EC2ResponseError, self.ec2.create_security_group, + security_group_name, 'test group') def test_group_name_valid_length_security_group(self): """Test that we sanely handle invalid security group names. @@ -406,16 +398,9 @@ class ApiEc2TestCase(test.TestCase): # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") for x in range(random.randint(256, 266))) - try: - self.ec2.create_security_group(security_group_name, 'test group') - except EC2ResponseError, e: - if e.code == 'InvalidParameterValue': - pass - else: - self.fail("Unexpected EC2ResponseError: %s " - "(expected InvalidParameterValue)" % e.code) - else: - self.fail('Exception not raised.') + + self.assertRaises(EC2ResponseError, self.ec2.create_security_group, + security_group_name, 'test group') def test_authorize_revoke_security_group_cidr(self): """ -- 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 ++-- nova/tests/test_cloud.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') 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': { diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 507b35d22..ac959bd63 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -1505,9 +1505,11 @@ class CloudTestCase(test.TestCase): 'ephemeral1': '/dev/sdd', 'ephemeral2': '/dev/sd3'} - self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref0), + self.assertEqual(self.cloud._format_instance_mapping(ctxt, + instance_ref0), cloud._DEFAULT_MAPPINGS) - self.assertEqual(self.cloud._get_instance_mapping(ctxt, instance_ref1), + self.assertEqual(self.cloud._format_instance_mapping(ctxt, + instance_ref1), expected) def test_describe_instance_attribute(self): -- 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 + nova/tests/api/openstack/test_extensions.py | 26 +- 6 files changed, 792 insertions(+), 20 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') 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 @@ + + + + + diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index d459c694f..2bc8bbb07 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -20,16 +20,20 @@ import os.path import stubout import unittest import webob -from xml.etree import ElementTree +from lxml import etree +from StringIO import StringIO from nova import context from nova import flags +from nova import utils from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil from nova.tests.api.openstack import fakes + FLAGS = flags.FLAGS NS = "{http://docs.openstack.org/compute/api/v1.1}" ATOMNS = "{http://www.w3.org/2005/Atom}" @@ -140,7 +144,7 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(200, response.status_int) print response.body - root = ElementTree.XML(response.body) + root = etree.XML(response.body) self.assertEqual(root.tag.split('extensions')[0], NS) # Make sure we have all the extensions. @@ -156,6 +160,8 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(fox_ext.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') + xmlutil.validate_schema(root, 'extensions') + def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) @@ -163,9 +169,10 @@ class ExtensionControllerTest(unittest.TestCase): request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) - print response.body + xml = response.body + print xml - root = ElementTree.XML(response.body) + root = etree.XML(xml) self.assertEqual(root.tag.split('extension')[0], NS) self.assertEqual(root.get('alias'), 'FOXNSOX') self.assertEqual(root.get('name'), 'Fox In Socks') @@ -175,6 +182,8 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(root.findtext('{0}description'.format(NS)), 'The Fox In Socks Extension') + xmlutil.validate_schema(root, 'extension') + class ResourceExtensionTest(unittest.TestCase): @@ -354,7 +363,8 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): } xml = serializer.serialize(data, 'show') - root = ElementTree.XML(xml) + print xml + root = etree.XML(xml) ext_dict = data['extension'] self.assertEqual(root.findtext('{0}description'.format(NS)), ext_dict['description']) @@ -368,6 +378,8 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): for key, value in link.items(): self.assertEqual(link_nodes[i].get(key), value) + xmlutil.validate_schema(root, 'extension') + def test_serialize_extensions(self): serializer = extensions.ExtensionsXMLSerializer() data = { @@ -415,7 +427,7 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): xml = serializer.serialize(data, 'index') print xml - root = ElementTree.XML(xml) + root = etree.XML(xml) ext_elems = root.findall('{0}extension'.format(NS)) self.assertEqual(len(ext_elems), 2) for i, ext_elem in enumerate(ext_elems): @@ -431,3 +443,5 @@ class ExtensionsXMLSerializerTest(unittest.TestCase): for i, link in enumerate(ext_dict['links']): for key, value in link.items(): self.assertEqual(link_nodes[i].get(key), value) + + xmlutil.validate_schema(root, 'extensions') -- cgit From 22b0e3948beaa2b1b3d61562e453412abb5edcbc Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 29 Jul 2011 03:42:40 -0400 Subject: Removing unnecessary imports. --- nova/tests/api/openstack/test_extensions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 2bc8bbb07..ae0dc283f 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -21,11 +21,9 @@ import stubout import unittest import webob from lxml import etree -from StringIO import StringIO from nova import context from nova import flags -from nova import utils from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors -- 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') 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 fe343a30ad5317ac5635667e72f56be775284658 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 5 Aug 2011 15:23:54 +0900 Subject: fix mismerge --- nova/virt/libvirt/connection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index c49f6da5d..7f5bdcf10 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1052,7 +1052,8 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(yamahata): # for nova.api.ec2.cloud.CloudController.get_metadata() xml_info['root_device'] = self.default_root_device - db.instance_update(context.get_admin_context(), instance['id'], + db.instance_update( + nova_context.get_admin_context(), instance['id'], {'root_device_name': '/dev/' + self.default_root_device}) swap = driver.block_device_info_get_swap(block_device_info) -- 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 ++-- nova/tests/api/openstack/test_server_actions.py | 40 +++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 16 deletions(-) (limited to 'nova') 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, diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 562cefe90..021b105d4 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -9,6 +9,7 @@ import webob from nova import context from nova import db from nova import utils +from nova import flags from nova.api.openstack import create_instance_helper from nova.compute import instance_types from nova.compute import power_state @@ -18,6 +19,9 @@ from nova.tests.api.openstack import common from nova.tests.api.openstack import fakes +FLAGS = flags.FLAGS + + def return_server_by_id(context, id): return _get_instance() @@ -370,6 +374,26 @@ class ServerActionsTest(test.TestCase): self.assertEqual(202, response.status_int) self.assertTrue(response.headers['Location']) + def test_create_backup_with_too_much_metadata(self): + self.flags(allow_admin_api=True) + + body = { + 'createBackup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + 'metadata': {'123': 'asdf'}, + }, + } + for num in range(FLAGS.quota_metadata_items + 1): + body['createBackup']['metadata']['foo%i' % num] = "bar" + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + def test_create_backup_no_name(self): """Name is required for backups""" self.flags(allow_admin_api=True) @@ -684,6 +708,22 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) + def test_create_image_with_too_much_metadata(self): + body = { + 'createImage': { + 'name': 'Snapshot 1', + 'metadata': {}, + }, + } + for num in range(FLAGS.quota_metadata_items + 1): + body['createImage']['metadata']['foo%i' % num] = "bar" + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + def test_create_image_no_name(self): body = { 'createImage': {}, -- cgit From 82eb299fd0fa6601d4704836ed7e76369f086ffc Mon Sep 17 00:00:00 2001 From: "Dave Walker (Daviey)" Date: Sat, 6 Aug 2011 20:18:35 +0100 Subject: simplified test cases further, thanks to trunk changes --- nova/tests/test_api.py | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 3af1563fa..533447362 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -342,12 +342,6 @@ class ApiEc2TestCase(test.TestCase): spaces, dashes, and underscores. """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') # Test block group_name of non alphanumeric characters, spaces, # dashes, and underscores. @@ -361,12 +355,6 @@ class ApiEc2TestCase(test.TestCase): API Spec states that the length should not exceed 255 chars """ self.expect_http() self.mox.ReplayAll() - user = self.manager.create_user('fake', 'fake', 'fake', admin=True) - project = self.manager.create_project('fake', 'fake', 'fake') - - # At the moment, you need both of these to actually be netadmin - self.manager.add_role('fake', 'netadmin') - project.add_role('fake', 'netadmin') # Test block group_name > 255 chars security_group_name = "".join(random.choice("poiuytrewqasdfghjklmnbvc") -- 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 +++- nova/tests/api/openstack/test_images.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'nova') 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 diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8e2e3f390..498e8b14c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -379,6 +379,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "updated": self.NOW_API_FORMAT, "created": self.NOW_API_FORMAT, "status": "ACTIVE", + "progress": 100, }, } @@ -402,6 +403,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "updated": self.NOW_API_FORMAT, "created": self.NOW_API_FORMAT, "status": "QUEUED", + "progress": 0, 'server': { 'id': 42, "links": [{ @@ -444,6 +446,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): updated="%(expected_now)s" created="%(expected_now)s" status="ACTIVE" + progress="100" xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) @@ -463,6 +466,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): updated="%(expected_now)s" created="%(expected_now)s" status="ACTIVE" + progress="100" xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) @@ -587,6 +591,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, }, { 'id': 124, @@ -594,6 +599,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', + 'progress': 0, }, { 'id': 125, @@ -608,7 +614,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'name': 'active snapshot', 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, - 'status': 'ACTIVE' + 'status': 'ACTIVE', + 'progress': 100, }, { 'id': 127, @@ -616,6 +623,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + 'progress': 0, }, { 'id': 129, @@ -623,6 +631,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, }] self.assertDictListMatch(expected, response_list) @@ -643,6 +652,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, "links": [{ "rel": "self", "href": "http://localhost/v1.1/images/123", @@ -662,6 +672,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', + 'progress': 0, 'server': { 'id': 42, "links": [{ @@ -723,6 +734,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, 'server': { 'id': 42, "links": [{ @@ -753,6 +765,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + 'progress': 0, 'server': { 'id': 42, "links": [{ @@ -780,6 +793,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100, "links": [{ "rel": "self", "href": "http://localhost/v1.1/images/129", @@ -1001,7 +1015,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_meta = json.loads(res.body)['image'] expected = {'id': 123, 'name': 'public image', 'updated': self.NOW_API_FORMAT, - 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE'} + 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', + 'progress': 100} self.assertDictMatch(image_meta, expected) def test_get_image_non_existent(self): -- 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') 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') 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 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 +++++++---------- nova/compute/api.py | 25 ++++++++++----- nova/db/api.py | 6 ++-- nova/db/sqlalchemy/api.py | 39 +++++++++++++++++------- nova/tests/api/openstack/test_server_metadata.py | 32 +++++++++---------- 5 files changed, 75 insertions(+), 55 deletions(-) (limited to 'nova') 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) diff --git a/nova/compute/api.py b/nova/compute/api.py index d2c08678b..646290e57 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1175,11 +1175,20 @@ class API(base.Base): """Delete the given metadata item from an instance.""" self.db.instance_metadata_delete(context, instance_id, key) - def update_or_create_instance_metadata(self, context, instance_id, - metadata): - """Updates or creates instance metadata.""" - combined_metadata = self.get_instance_metadata(context, instance_id) - combined_metadata.update(metadata) - self._check_metadata_properties_quota(context, combined_metadata) - self.db.instance_metadata_update_or_create(context, instance_id, - metadata) + def update_instance_metadata(self, context, instance_id, + metadata, delete=False): + """Updates or creates instance metadata. + + If delete is True, metadata items that are not specified in the + `metadata` argument will be deleted. + + """ + if not delete: + _metadata = self.get_instance_metadata(context, instance_id) + _metadata.update(metadata) + else: + _metadata = metadata + + self._check_metadata_properties_quota(context, _metadata) + + self.db.instance_metadata_update(context, instance_id, _metadata, True) diff --git a/nova/db/api.py b/nova/db/api.py index 47308bdba..90003cf86 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1381,9 +1381,9 @@ def instance_metadata_delete(context, instance_id, key): IMPL.instance_metadata_delete(context, instance_id, key) -def instance_metadata_update_or_create(context, instance_id, metadata): - """Create or update instance metadata.""" - IMPL.instance_metadata_update_or_create(context, instance_id, metadata) +def instance_metadata_update(context, instance_id, metadata, delete): + """Update metadata if it exists, otherwise create it.""" + IMPL.instance_metadata_update(context, instance_id, metadata) #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 003eb8bcb..5ae14261c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1345,9 +1345,10 @@ def instance_update(context, instance_id, values): session = get_session() metadata = values.get('metadata') if metadata is not None: - instance_metadata_delete_all(context, instance_id) - instance_metadata_update_or_create(context, instance_id, - values.pop('metadata')) + instance_metadata_update(context, + instance_id, + values.pop('metadata'), + delete=True) with session.begin(): if utils.is_uuid_like(instance_id): instance_ref = instance_get_by_uuid(context, instance_id, @@ -3201,21 +3202,37 @@ def instance_metadata_get_item(context, instance_id, key, session=None): @require_context @require_instance_exists -def instance_metadata_update_or_create(context, instance_id, metadata): +def instance_metadata_update(context, instance_id, metadata, delete): session = get_session() - original_metadata = instance_metadata_get(context, instance_id) + # Set all metadata that isn't passed in if delete kwarg is True + if delete: + original_metadata = instance_metadata_get(context, instance_id) + for meta in original_metadata.iteritems(): + if meta.key not in metadata: + meta.update({"deleted": True}) + meta.save(session=session) meta_ref = None - for key, value in metadata.iteritems(): + + # Now update all existing items with new values, or create new meta objects + for meta_key, meta_value in metadata.iteritems(): + + # update the value whether it exists or not + item = {"value": meta_value} + + # if the metadata item exists, make sure it is not delete try: - meta_ref = instance_metadata_get_item(context, instance_id, key, - session) + meta_ref = instance_metadata_get_item(context, instance_id, + meta_key, session) + item.update({"deleted": False}) + + # if the item doesn't exist, we also need to set key and instance_id except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() - meta_ref.update({"key": key, "value": value, - "instance_id": instance_id, - "deleted": False}) + item.update({"key": meta_key, "instance_id": instance_id}) + + meta_ref.update(item) meta_ref.save(session=session) return metadata diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 08a6a062a..a90d572c8 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -29,11 +29,11 @@ import nova.wsgi FLAGS = flags.FLAGS -def return_create_instance_metadata_max(context, server_id, metadata): +def return_create_instance_metadata_max(context, server_id, metadata, delete): return stub_max_server_metadata() -def return_create_instance_metadata(context, server_id, metadata): +def return_create_instance_metadata(context, server_id, metadata, delete): return stub_server_metadata() @@ -202,7 +202,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(404, res.status_int) def test_create(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' @@ -216,7 +216,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(expected, res_dict) def test_create_xml(self): - self.stubs.Set(nova.db.api, "instance_metadata_update_or_create", + self.stubs.Set(nova.db.api, "instance_metadata_update", return_create_instance_metadata) req = webob.Request.blank("/v1.1/servers/1/metadata") req.method = "POST" @@ -240,7 +240,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(request_metadata.toxml(), actual_metadata.toxml()) def test_create_empty_body(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' @@ -258,7 +258,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(404, res.status_int) def test_update_all(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'PUT' @@ -276,7 +276,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(expected, res_dict) def test_update_all_empty_container(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'PUT' @@ -289,7 +289,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(expected, res_dict) def test_update_all_malformed_container(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'PUT' @@ -300,7 +300,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_all_malformed_data(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'PUT' @@ -320,7 +320,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(404, res.status_int) def test_update_item(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' @@ -334,7 +334,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(expected, res_dict) def test_update_item_xml(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key9') req.method = 'PUT' @@ -361,7 +361,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(404, res.status_int) def test_update_item_empty_body(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' @@ -370,7 +370,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' @@ -380,7 +380,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/bad') req.method = 'PUT' @@ -390,7 +390,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_too_many_metadata_items_on_create(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) data = {"metadata": {}} for num in range(FLAGS.quota_metadata_items + 1): @@ -404,7 +404,7 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_to_many_metadata_items_on_update_item(self): - self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata_max) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' -- 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 +++++++++++------- nova/db/api.py | 2 +- nova/db/sqlalchemy/api.py | 12 ++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) (limited to 'nova') 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.""" diff --git a/nova/db/api.py b/nova/db/api.py index 90003cf86..0516c683f 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1383,7 +1383,7 @@ def instance_metadata_delete(context, instance_id, key): def instance_metadata_update(context, instance_id, metadata, delete): """Update metadata if it exists, otherwise create it.""" - IMPL.instance_metadata_update(context, instance_id, metadata) + IMPL.instance_metadata_update(context, instance_id, metadata, delete) #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5ae14261c..2e29f34ac 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3208,10 +3208,12 @@ def instance_metadata_update(context, instance_id, metadata, delete): # Set all metadata that isn't passed in if delete kwarg is True if delete: original_metadata = instance_metadata_get(context, instance_id) - for meta in original_metadata.iteritems(): - if meta.key not in metadata: - meta.update({"deleted": True}) - meta.save(session=session) + for meta_key, meta_value in original_metadata.iteritems(): + if meta_key not in metadata: + meta_ref = instance_metadata_get_item(context, instance_id, + meta_key, session) + meta_ref.update({'deleted': True}) + meta_ref.save(session=session) meta_ref = None @@ -3221,11 +3223,9 @@ def instance_metadata_update(context, instance_id, metadata, delete): # update the value whether it exists or not item = {"value": meta_value} - # if the metadata item exists, make sure it is not delete try: meta_ref = instance_metadata_get_item(context, instance_id, meta_key, session) - item.update({"deleted": False}) # if the item doesn't exist, we also need to set key and instance_id except exception.InstanceMetadataNotFound, e: -- 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') 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 nova/utils.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 nova/api/openstack/schemas/__init__.py delete mode 100644 nova/api/openstack/schemas/v1.1/__init__.py (limited to 'nova') 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 diff --git a/nova/utils.py b/nova/utils.py index 4ea623cc1..da8826f1f 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -223,7 +223,7 @@ def abspath(s): def novadir(): import nova - return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] + return os.path.abspath(nova.__file__).split('nova/__init__.py')[0] def default_flagfile(filename='nova.conf', args=None): -- 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 +++ nova/tests/api/openstack/test_servers.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') 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 diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index fd06b2e64..ca8dfc5b9 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1356,7 +1356,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) + self.assertEqual(res.status_int, 202) server = json.loads(res.body)['server'] self.assertEqual(16, len(server['adminPass'])) self.assertEqual(1, server['id']) @@ -1451,7 +1451,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) + self.assertEqual(res.status_int, 202) server = json.loads(res.body)['server'] self.assertEqual(expected_flavor, server['flavor']) self.assertEqual(expected_image, server['image']) @@ -1496,7 +1496,7 @@ class ServersTest(test.TestCase): req.body = json.dumps(body) req.headers['content-type'] = "application/json" res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) + self.assertEqual(res.status_int, 202) server = json.loads(res.body)['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) -- cgit From 44fc059d8bf8e57a808d69ba3b5c9a4235707d34 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 9 Aug 2011 00:47:16 -0400 Subject: updating more test cases --- nova/tests/api/openstack/test_servers.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ca8dfc5b9..b411a5e4f 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1159,7 +1159,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) + self.assertEqual(res.status_int, 202) server = json.loads(res.body)['server'] self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) @@ -2513,13 +2513,13 @@ class TestServerInstanceCreation(test.TestCase): def test_create_instance_with_no_personality(self): request, response, injected_files = \ self._create_instance_with_personality_json(personality=None) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, []) def test_create_instance_with_no_personality_xml(self): request, response, injected_files = \ self._create_instance_with_personality_xml(personality=None) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, []) def test_create_instance_with_personality(self): @@ -2529,7 +2529,7 @@ class TestServerInstanceCreation(test.TestCase): personality = [(path, b64contents)] request, response, injected_files = \ self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, [(path, contents)]) def test_create_instance_with_personality_xml(self): @@ -2539,7 +2539,7 @@ class TestServerInstanceCreation(test.TestCase): personality = [(path, b64contents)] request, response, injected_files = \ self._create_instance_with_personality_xml(personality) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, [(path, contents)]) def test_create_instance_with_personality_no_path(self): @@ -2602,7 +2602,7 @@ class TestServerInstanceCreation(test.TestCase): request = self._get_create_request_json(body_dict) compute_api, response = \ self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) def test_create_instance_with_three_personalities(self): files = [ @@ -2615,7 +2615,7 @@ class TestServerInstanceCreation(test.TestCase): personality.append((path, base64.b64encode(content))) request, response, injected_files = \ self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, files) def test_create_instance_personality_empty_content(self): @@ -2624,13 +2624,13 @@ class TestServerInstanceCreation(test.TestCase): personality = [(path, contents)] request, response, injected_files = \ self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) self.assertEquals(injected_files, [(path, contents)]) def test_create_instance_admin_pass_json(self): request, response, dummy = \ self._create_instance_with_personality_json(None) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) response = json.loads(response.body) self.assertTrue('adminPass' in response['server']) self.assertEqual(16, len(response['server']['adminPass'])) @@ -2638,7 +2638,7 @@ class TestServerInstanceCreation(test.TestCase): def test_create_instance_admin_pass_xml(self): request, response, dummy = \ self._create_instance_with_personality_xml(None) - self.assertEquals(response.status_int, 200) + self.assertEquals(response.status_int, 202) dom = minidom.parseString(response.body) server = dom.childNodes[0] self.assertEquals(server.nodeName, 'server') -- 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 ++++++++++++------- nova/compute/api.py | 2 ++ nova/db/sqlalchemy/api.py | 2 +- nova/tests/api/openstack/test_server_metadata.py | 30 ++++++++++++++++++------ 4 files changed, 41 insertions(+), 17 deletions(-) (limited to 'nova') 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) diff --git a/nova/compute/api.py b/nova/compute/api.py index 646290e57..867f6ce99 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1192,3 +1192,5 @@ class API(base.Base): self._check_metadata_properties_quota(context, _metadata) self.db.instance_metadata_update(context, instance_id, _metadata, True) + + return _metadata diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2e29f34ac..24ea23611 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3205,7 +3205,7 @@ def instance_metadata_get_item(context, instance_id, key, session=None): def instance_metadata_update(context, instance_id, metadata, delete): session = get_session() - # Set all metadata that isn't passed in if delete kwarg is True + # Set existing metadata to deleted if delete argument is True if delete: original_metadata = instance_metadata_get(context, instance_id) for meta_key, meta_value in original_metadata.iteritems(): diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index a90d572c8..cb43c58f6 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -202,20 +202,29 @@ class ServerMetaDataTest(test.TestCase): self.assertEqual(404, res.status_int) def test_create(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.content_type = "application/json" - expected = {"metadata": {"key1": "value1"}} - req.body = json.dumps(expected) + input = {"metadata": {"key9": "value9"}} + req.body = json.dumps(input) res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual(expected, res_dict) + input['metadata'].update({ + "key1": "value1", + "key2": "value2", + "key3":"value3", + }) + self.assertEqual(input, res_dict) def test_create_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) self.stubs.Set(nova.db.api, "instance_metadata_update", return_create_instance_metadata) req = webob.Request.blank("/v1.1/servers/1/metadata") @@ -225,19 +234,26 @@ class ServerMetaDataTest(test.TestCase): request_metadata = minidom.parseString(""" - value3 - value2 - value1 + value5 """.replace(" ", "").replace("\n", "")) req.body = str(request_metadata.toxml()) response = req.get_response(fakes.wsgi_app()) + expected_metadata = minidom.parseString(""" + + value3 + value2 + value1 + value5 + + """.replace(" ", "").replace("\n", "")) + self.assertEqual(200, response.status_int) actual_metadata = minidom.parseString(response.body) - self.assertEqual(request_metadata.toxml(), actual_metadata.toxml()) + self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', -- cgit From f80ac0c7404882fa0f3e640d1330ab37e6da797a Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 15:44:43 +0100 Subject: Fix remaining two pep8 violations --- nova/tests/api/openstack/test_server_actions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index bf18bc1b0..311db3f42 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -943,9 +943,7 @@ class TestServerActionXMLDeserializerV11(test.TestCase): flavorRef="http://localhost/flavors/3"/>""" request = self.deserializer.deserialize(serial_request, 'action') expected = { - "resize": { - "flavorRef": "http://localhost/flavors/3" - }, + "resize": {"flavorRef": "http://localhost/flavors/3"}, } self.assertEquals(request['body'], expected) -- 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 ++++++--- nova/compute/api.py | 8 +++----- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'nova') 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} diff --git a/nova/compute/api.py b/nova/compute/api.py index 867f6ce99..aaff8b370 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1183,14 +1183,12 @@ class API(base.Base): `metadata` argument will be deleted. """ - if not delete: + if delete: + _metadata = metadata + else: _metadata = self.get_instance_metadata(context, instance_id) _metadata.update(metadata) - else: - _metadata = metadata self._check_metadata_properties_quota(context, _metadata) - self.db.instance_metadata_update(context, instance_id, _metadata, True) - return _metadata -- cgit From 73a26895d850d717d5bd5f106edc6c9ae09218a4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 9 Aug 2011 11:47:46 -0400 Subject: fixing one pep8 failure --- nova/tests/api/openstack/test_server_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index cb43c58f6..ec446f0f0 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -218,7 +218,7 @@ class ServerMetaDataTest(test.TestCase): input['metadata'].update({ "key1": "value1", "key2": "value2", - "key3":"value3", + "key3": "value3", }) self.assertEqual(input, res_dict) -- cgit From 47229cb10c7a322755d36229649c9d3e5712592d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 9 Aug 2011 17:32:39 +0000 Subject: Be more tolerant of agent failures. The instance still booted (most likely) so don't treat it like it didn't --- nova/virt/xenapi/vmops.py | 90 ++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 44 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b913e764e..50aa0d3b2 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -282,6 +282,7 @@ class VMOps(object): 'architecture': instance.architecture}) def _check_agent_version(): + LOG.debug(_("Querying agent version")) if instance.os_type == 'windows': # Windows will generally perform a setup process on first boot # that can take a couple of minutes and then reboot. So we @@ -292,7 +293,6 @@ class VMOps(object): else: version = self.get_agent_version(instance) if not version: - LOG.info(_('No agent version returned by instance')) return LOG.info(_('Instance agent version: %s') % version) @@ -327,6 +327,10 @@ class VMOps(object): LOG.debug(_("Setting admin password")) self.set_admin_password(instance, admin_password) + def _reset_network(): + LOG.debug(_("Resetting network")) + self.reset_network(instance, vm_ref) + # NOTE(armando): Do we really need to do this in virt? # NOTE(tr3buchet): not sure but wherever we do it, we need to call # reset_network afterwards @@ -341,7 +345,7 @@ class VMOps(object): _check_agent_version() _inject_files() _set_admin_password() - self.reset_network(instance, vm_ref) + _reset_network() return True except Exception, exc: LOG.warn(exc) @@ -597,13 +601,13 @@ class VMOps(object): transaction_id = str(uuid.uuid4()) args = {'id': transaction_id} resp = self._make_agent_call('version', instance, '', args) - if resp is None: - # No response from the agent - return - resp_dict = json.loads(resp) + if resp['returncode'] != '0': + LOG.error(_('Failed to query agent version: %(resp)r') % + locals()) + return None # Some old versions of the Windows agent have a trailing \\r\\n # (ie CRLF escaped) for some reason. Strip that off. - return resp_dict['message'].replace('\\r\\n', '') + return resp['message'].replace('\\r\\n', '') if timeout: vm_ref = self._get_vm_opaque_ref(instance) @@ -634,13 +638,10 @@ class VMOps(object): transaction_id = str(uuid.uuid4()) args = {'id': transaction_id, 'url': url, 'md5sum': md5sum} resp = self._make_agent_call('agentupdate', instance, '', args) - if resp is None: - # No response from the agent - return - resp_dict = json.loads(resp) - if resp_dict['returncode'] != '0': - raise RuntimeError(resp_dict['message']) - return resp_dict['message'] + if resp['returncode'] != '0': + LOG.error(_('Failed to update agent: %(resp)r') % locals()) + return None + return resp['message'] def set_admin_password(self, instance, new_pass): """Set the root/admin password on the VM instance. @@ -659,18 +660,13 @@ class VMOps(object): key_init_args = {'id': key_init_transaction_id, 'pub': str(dh.get_public())} resp = self._make_agent_call('key_init', instance, '', key_init_args) - if resp is None: - # No response from the agent - return - resp_dict = json.loads(resp) # Successful return code from key_init is 'D0' - if resp_dict['returncode'] != 'D0': - # There was some sort of error; the message will contain - # a description of the error. - raise RuntimeError(resp_dict['message']) + if resp['returncode'] != 'D0': + LOG.error(_('Failed to exchange keys: %(resp)r') % locals()) + return None # Some old versions of the Windows agent have a trailing \\r\\n # (ie CRLF escaped) for some reason. Strip that off. - agent_pub = int(resp_dict['message'].replace('\\r\\n', '')) + agent_pub = int(resp['message'].replace('\\r\\n', '')) dh.compute_shared(agent_pub) # Some old versions of Linux and Windows agent expect trailing \n # on password to work correctly. @@ -679,17 +675,14 @@ class VMOps(object): password_transaction_id = str(uuid.uuid4()) password_args = {'id': password_transaction_id, 'enc_pass': enc_pass} resp = self._make_agent_call('password', instance, '', password_args) - if resp is None: - # No response from the agent - return - resp_dict = json.loads(resp) # Successful return code from password is '0' - if resp_dict['returncode'] != '0': - raise RuntimeError(resp_dict['message']) + if resp['returncode'] != '0': + LOG.error(_('Failed to update password: %(resp)r') % locals()) + return None db.instance_update(nova_context.get_admin_context(), instance['id'], dict(admin_pass=new_pass)) - return resp_dict['message'] + return resp['message'] def inject_file(self, instance, path, contents): """Write a file to the VM instance. @@ -712,12 +705,10 @@ class VMOps(object): # If the agent doesn't support file injection, a NotImplementedError # will be raised with the appropriate message. resp = self._make_agent_call('inject_file', instance, '', args) - resp_dict = json.loads(resp) - if resp_dict['returncode'] != '0': - # There was some other sort of error; the message will contain - # a description of the error. - raise RuntimeError(resp_dict['message']) - return resp_dict['message'] + if resp['returncode'] != '0': + LOG.error(_('Failed to inject file: %(resp)r') % locals()) + return None + return resp['message'] def _shutdown(self, instance, vm_ref, hard=True): """Shutdown an instance.""" @@ -1178,8 +1169,19 @@ class VMOps(object): def _make_agent_call(self, method, vm, path, addl_args=None): """Abstracts out the interaction with the agent xenapi plugin.""" - return self._make_plugin_call('agent', method=method, vm=vm, + ret = self._make_plugin_call('agent', method=method, vm=vm, path=path, addl_args=addl_args) + if isinstance(ret, dict): + return ret + try: + return json.loads(ret) + except TypeError: + instance_id = vm.id + LOG.error(_('The agent call to %(method)s returned an invalid' + ' response: %(ret)r. VM id=%(instance_id)s;' + ' path=%(path)s; args=%(addl_args)r') % locals()) + return {'returncode': 'error', + 'message': 'unable to deserialize response'} def _make_plugin_call(self, plugin, method, vm, path, addl_args=None, vm_ref=None): @@ -1197,20 +1199,20 @@ class VMOps(object): ret = self._session.wait_for_task(task, instance_id) except self.XenAPI.Failure, e: ret = None - err_trace = e.details[-1] - err_msg = err_trace.splitlines()[-1] - strargs = str(args) + err_msg = e.details[-1].splitlines()[-1] if 'TIMEOUT:' in err_msg: LOG.error(_('TIMEOUT: The call to %(method)s timed out. ' - 'VM id=%(instance_id)s; args=%(strargs)s') % locals()) + 'VM id=%(instance_id)s; args=%(args)r') % locals()) + return {'returncode': 'timeout', 'message': err_msg} elif 'NOT IMPLEMENTED:' in err_msg: LOG.error(_('NOT IMPLEMENTED: The call to %(method)s is not' ' supported by the agent. VM id=%(instance_id)s;' - ' args=%(strargs)s') % locals()) - raise NotImplementedError(err_msg) + ' args=%(args)r') % locals()) + return {'returncode': 'notimplemented', 'message': err_msg} else: LOG.error(_('The call to %(method)s returned an error: %(e)s. ' - 'VM id=%(instance_id)s; args=%(strargs)s') % locals()) + 'VM id=%(instance_id)s; args=%(args)r') % locals()) + return {'returncode': 'error', 'message': err_msg} return ret def add_to_xenstore(self, vm, path, key, value): -- 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 ++++++++++++++++++++++++++ nova/compute/api.py | 6 ++++++ nova/compute/manager.py | 6 ++++++ nova/tests/test_hosts.py | 32 ++++++++++++++++++++++++++++---- nova/virt/driver.py | 4 ++++ nova/virt/fake.py | 4 ++++ nova/virt/hyperv.py | 4 ++++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/vmwareapi_conn.py | 4 ++++ nova/virt/xenapi/vmops.py | 21 ++++++++++++++++++--- nova/virt/xenapi_conn.py | 4 ++++ 11 files changed, 108 insertions(+), 7 deletions(-) (limited to 'nova') 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): diff --git a/nova/compute/api.py b/nova/compute/api.py index b0eedcd64..71e11c5ea 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -917,6 +917,12 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) + def set_power_state(self, context, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + return self._call_compute_message("set_power_state", context, + instance_id=None, host=host, + params={"power_state": power_state}) + @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 91a604934..eb8e4df3c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -880,6 +880,12 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) + @exception.wrap_exception + def set_power_state(self, context, instance_id=None, host=None, + power_state=None): + """Turns the specified host on/off, or reboots the host.""" + return self.driver.set_power_state(host, power_state) + @exception.wrap_exception def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 548f81f8b..5a52e36e2 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,6 +48,16 @@ def stub_set_host_enabled(context, host, enabled): return status +def stub_set_power_state(context, host, power_state): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + if host == "host_c1": + return power_state + else: + return "fail" + + class FakeRequest(object): environ = {"nova.context": context.get_admin_context()} @@ -62,6 +72,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'set_power_state', + stub_set_power_state) def test_list_hosts(self): """Verify that the compute hosts are returned.""" @@ -87,15 +99,27 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") + def test_power_state(self): + en_body = {"power_state": "reboot"} + result_c1 = self.controller.update(self.req, "host_c1", body=en_body) + self.assertEqual(result_c1["power_state"], "reboot") + result_c2 = self.controller.update(self.req, "host_c2", body=en_body) + self.assertEqual(result_c2["power_state"], "fail") + + def test_bad_power_state_value(self): + bad_body = {"power_state": "bad"} + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + def test_bad_status_value(self): bad_body = {"status": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_update_key(self): bad_body = {"crazy": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_host(self): self.assertRaises(exception.HostNotFound, self.controller.update, diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 3c4a073bf..eed32d8d6 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -253,3 +253,7 @@ class ComputeDriver(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..0596079e8 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -518,3 +518,7 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..a438ff2e8 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -503,3 +503,7 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index b80a3daee..2a02e5a2d 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1595,3 +1595,7 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index d80e14931..0136225dd 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -194,6 +194,10 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass + class VMWareAPISession(object): """ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cb96930c1..ec90ba9fe 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -936,10 +936,25 @@ class VMOps(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" args = {"enabled": json.dumps(enabled)} - json_resp = self._call_xenhost("set_host_enabled", args) - resp = json.loads(json_resp) + xenapi_resp = self._call_xenhost("set_host_enabled", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] return resp["status"] + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + args = {"power_state": power_state} + xenapi_resp = self._call_xenhost("set_power_state", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] + return resp["power_state"] + def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts @@ -953,7 +968,7 @@ class VMOps(object): #args={"params": arg_dict}) ret = self._session.wait_for_task(task, task_id) except self.XenAPI.Failure as e: - ret = None + ret = e LOG.error(_("The call to %(method)s returned an error: %(e)s.") % locals()) return ret diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..0b88e0999 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -340,6 +340,10 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + return self._vmops.set_power_state(host, power_state) + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From 60a9763382ccd77735a75b6047c821477eab684e Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 7 Jul 2011 15:36:39 +0000 Subject: pep8 fixes --- nova/tests/test_hosts.py | 16 ++++++++-------- nova/virt/xenapi/vmops.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 5a52e36e2..417737638 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -103,23 +103,23 @@ class HostTestCase(test.TestCase): en_body = {"power_state": "reboot"} result_c1 = self.controller.update(self.req, "host_c1", body=en_body) self.assertEqual(result_c1["power_state"], "reboot") - result_c2 = self.controller.update(self.req, "host_c2", body=en_body) - self.assertEqual(result_c2["power_state"], "fail") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c2", body=en_body) def test_bad_power_state_value(self): bad_body = {"power_state": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_status_value(self): bad_body = {"status": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_update_key(self): bad_body = {"crazy": "bad"} - result = self.controller.update(self.req, "host_c1", body=bad_body) - self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) def test_bad_host(self): self.assertRaises(exception.HostNotFound, self.controller.update, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ec90ba9fe..aec802eff 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -941,7 +941,7 @@ class VMOps(object): resp = json.loads(xenapi_resp) except TypeError as e: # Already logged; return the message - return xenapi_resp.details[-1] + return xenapi_resp.details[-1] return resp["status"] def set_power_state(self, host, power_state): @@ -952,7 +952,7 @@ class VMOps(object): resp = json.loads(xenapi_resp) except TypeError as e: # Already logged; return the message - return xenapi_resp.details[-1] + return xenapi_resp.details[-1] return resp["power_state"] def _call_xenhost(self, method, arg_dict): -- cgit From 8d97118be776fcaad3053d1f93f61d339685a4ae Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Fri, 22 Jul 2011 17:26:11 +0400 Subject: Moved restaring instances from livbirt driver to ComputeManager. --- nova/compute/manager.py | 19 +++++++++++++++++++ nova/flags.py | 3 +++ nova/virt/libvirt/connection.py | 25 ++----------------------- 3 files changed, 24 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5819a520a..c7d3004a5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -146,7 +146,26 @@ class ComputeManager(manager.SchedulerDependentManager): def init_host(self): """Initialization for a standalone compute service.""" + # NOTE(nsokolov): based on itoumsn's implementation from libvirt driver + from nova import context self.driver.init_host(host=self.host) + admin_context = context.get_admin_context() + for instance in self.db.instance_get_all_by_host(admin_context, self.host): + try: + LOG.debug(_('Checking state of %s'), instance['name']) + state = self.driver.get_info(instance['name'])['state'] + except exception.NotFound: + state = power_state.SHUTOFF + + LOG.debug(_('Current state of %(name)s is %(state)s, state in DB is %(db_state)s.'), + {'name': instance['name'], 'state': state, 'db_state': instance['state']}) + + if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ + and FLAGS.start_guests_on_host_boot: + LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) + self.reboot_instance(admin_context, instance[id]) + else: + self.db.instance_set_state(ctxt, instance['id'], state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" diff --git a/nova/flags.py b/nova/flags.py index 49355b436..23ca38b17 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -387,3 +387,6 @@ DEFINE_list('zone_capabilities', 'Key/Multi-value list representng capabilities of this zone') DEFINE_string('build_plan_encryption_key', None, '128bit (hex) encryption key for scheduler build plans.') + +DEFINE_bool('start_guests_on_host_boot', False, + 'Whether to restart guests when the host reboots') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..d85b91ee2 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -121,8 +121,6 @@ flags.DEFINE_integer('live_migration_bandwidth', 0, 'Define live migration behavior') flags.DEFINE_string('qemu_img', 'qemu-img', 'binary to use for qemu-img commands') -flags.DEFINE_bool('start_guests_on_host_boot', False, - 'Whether to restart guests when the host reboots') def get_connection(read_only): @@ -167,27 +165,8 @@ class LibvirtConnection(driver.ComputeDriver): self.firewall_driver = fw_class(get_connection=self._get_connection) def init_host(self, host): - # Adopt existing VM's running here - ctxt = context.get_admin_context() - for instance in db.instance_get_all_by_host(ctxt, host): - try: - LOG.debug(_('Checking state of %s'), instance['name']) - state = self.get_info(instance['name'])['state'] - except exception.NotFound: - state = power_state.SHUTOFF - - LOG.debug(_('Current state of %(name)s was %(state)s.'), - {'name': instance['name'], 'state': state}) - db.instance_set_state(ctxt, instance['id'], state) - - # NOTE(justinsb): We no longer delete SHUTOFF instances, - # the user may want to power them back on - - if state != power_state.RUNNING: - continue - self.firewall_driver.setup_basic_filtering(instance) - self.firewall_driver.prepare_instance_filter(instance) - self.firewall_driver.apply_instance_filter(instance) + # NOTE(nsokolov): moved instance restarting to ComputeManager + pass def _get_connection(self): if not self._wrapped_conn or not self._test_connection(): -- cgit From 4937c2f2c757776eacba20a6446c059c4938d6b8 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 11:06:06 +0400 Subject: Removed driver-specific autostart code. --- nova/virt/libvirt/connection.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index d85b91ee2..65a6b6393 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -571,11 +571,6 @@ class LibvirtConnection(driver.ComputeDriver): LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) - if FLAGS.start_guests_on_host_boot: - LOG.debug(_("instance %s: setting autostart ON") % - instance['name']) - domain.setAutostart(1) - def _wait_for_boot(): """Called at an interval until the VM is running.""" instance_name = instance['name'] -- cgit From 708b0cb65a672e9f6b8bab4817061be4fa2a8928 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:22:38 +0400 Subject: Fixed init_host context name. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c7d3004a5..b876e87d0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -165,7 +165,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) self.reboot_instance(admin_context, instance[id]) else: - self.db.instance_set_state(ctxt, instance['id'], state) + self.db.instance_set_state(admin_context, instance['id'], state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From d8f4d773b08a94b171ff2643d48daa5b2709118a Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:33:45 +0400 Subject: Fixed id. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b876e87d0..b7e54d903 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -163,7 +163,7 @@ class ComputeManager(manager.SchedulerDependentManager): if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ and FLAGS.start_guests_on_host_boot: LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) - self.reboot_instance(admin_context, instance[id]) + self.reboot_instance(admin_context, instance['id']) else: self.db.instance_set_state(admin_context, instance['id'], state) -- cgit From 19379c78e6efd4637d876c91b022e6e7dbd38836 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 14:37:21 +0400 Subject: Fixed logging. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b7e54d903..071eabcfe 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -162,7 +162,7 @@ class ComputeManager(manager.SchedulerDependentManager): if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.')) + LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.'), {'name': instance['name']}) self.reboot_instance(admin_context, instance['id']) else: self.db.instance_set_state(admin_context, instance['id'], state) -- cgit From cf4aea379eb337b16a9816d45c50c0553c500d0d Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 15:03:02 +0400 Subject: pep8 --- nova/compute/manager.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 071eabcfe..ebd91177f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -150,22 +150,29 @@ class ComputeManager(manager.SchedulerDependentManager): from nova import context self.driver.init_host(host=self.host) admin_context = context.get_admin_context() - for instance in self.db.instance_get_all_by_host(admin_context, self.host): + for instance in self.db.instance_get_all_by_host(admin_context, + self.host): try: LOG.debug(_('Checking state of %s'), instance['name']) state = self.driver.get_info(instance['name'])['state'] except exception.NotFound: state = power_state.SHUTOFF - LOG.debug(_('Current state of %(name)s is %(state)s, state in DB is %(db_state)s.'), - {'name': instance['name'], 'state': state, 'db_state': instance['state']}) - - if instance['state'] == power_state.RUNNING and state != power_state.RUNNING \ - and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute restart.'), {'name': instance['name']}) + LOG.debug(_('Current state of %(name)s is %(state)s, state in ' + 'DB is %(db_state)s.'), {'name': instance['name'], + 'state': state, + 'db_state': + instance['state']}) + + if instance['state'] == power_state.RUNNING \ + and state != power_state.RUNNING \ + and FLAGS.start_guests_on_host_boot: + LOG.debug(_('Rebooting instance %(name)s after nova-compute ' + ' restart.'), {'name': instance['name']}) self.reboot_instance(admin_context, instance['id']) else: - self.db.instance_set_state(admin_context, instance['id'], state) + self.db.instance_set_state(admin_context, instance['id'], + state) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From d85a43c4cdb1bfd28355ded486af2ded8f43d6b0 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 19:54:23 +0400 Subject: Some estetic refactoring. --- nova/compute/manager.py | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ebd91177f..950fe0d53 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -44,6 +44,7 @@ import functools from eventlet import greenthread +import nova.context from nova import exception from nova import flags import nova.image @@ -145,34 +146,26 @@ class ComputeManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Initialization for a standalone compute service.""" - # NOTE(nsokolov): based on itoumsn's implementation from libvirt driver - from nova import context + """Initialization for a standalone compute service. + + Reboots instances marked as running in DB if they is not running.""" self.driver.init_host(host=self.host) - admin_context = context.get_admin_context() - for instance in self.db.instance_get_all_by_host(admin_context, - self.host): - try: - LOG.debug(_('Checking state of %s'), instance['name']) - state = self.driver.get_info(instance['name'])['state'] - except exception.NotFound: - state = power_state.SHUTOFF - - LOG.debug(_('Current state of %(name)s is %(state)s, state in ' - 'DB is %(db_state)s.'), {'name': instance['name'], - 'state': state, - 'db_state': - instance['state']}) - - if instance['state'] == power_state.RUNNING \ - and state != power_state.RUNNING \ - and FLAGS.start_guests_on_host_boot: - LOG.debug(_('Rebooting instance %(name)s after nova-compute ' - ' restart.'), {'name': instance['name']}) - self.reboot_instance(admin_context, instance['id']) - else: - self.db.instance_set_state(admin_context, instance['id'], - state) + context = nova.context.get_admin_context() + instances = self.db.instance_get_all_by_host(context, self.host) + for instance in instances: + inst_name = instance['name'] + db_state = instance['state'] + drv_state = self._update_state(context, instance['id']) + + expect_running = db_state == power_state.RUNNING != drv_state + + LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' + 'state in DB is %(db_state)s.'), locals()) + + if expect_running and FLAGS.start_guests_on_host_boot: + LOG.info(_('Rebooting instance %(inst_name)s after ' + 'nova-compute restart.'), locals()) + self.reboot_instance(context, instance['id']) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" @@ -180,6 +173,7 @@ class ComputeManager(manager.SchedulerDependentManager): if state is None: try: + LOG.debug(_('Checking state of %s'), instance_ref['name']) info = self.driver.get_info(instance_ref['name']) except exception.NotFound: info = None -- cgit From 9c88bbee56dd05703af8f7c0df839a4da73f491a Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Mon, 25 Jul 2011 19:59:00 +0400 Subject: Hotfix. --- nova/compute/manager.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 950fe0d53..79e9b16d3 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -184,6 +184,7 @@ class ComputeManager(manager.SchedulerDependentManager): state = power_state.FAILED self.db.instance_set_state(context, instance_id, state) + return state def _update_launched_at(self, context, instance_id, launched_at=None): """Update the launched_at parameter of the given instance.""" -- cgit From ba4946d0d3c73e5d9f67f42203d103bf98563458 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 00:31:42 +0400 Subject: Fixed old libvirt semantics, added resume_guests_state_on_host_boot flag. --- nova/compute/manager.py | 9 ++++----- nova/flags.py | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 79e9b16d3..bfac3df28 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -146,9 +146,7 @@ class ComputeManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Initialization for a standalone compute service. - - Reboots instances marked as running in DB if they is not running.""" + """Initialization for a standalone compute service.""" self.driver.init_host(host=self.host) context = nova.context.get_admin_context() instances = self.db.instance_get_all_by_host(context, self.host) @@ -157,12 +155,13 @@ class ComputeManager(manager.SchedulerDependentManager): db_state = instance['state'] drv_state = self._update_state(context, instance['id']) - expect_running = db_state == power_state.RUNNING != drv_state + expect_running = (db_state == power_state.RUNNING != drv_state) LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' 'state in DB is %(db_state)s.'), locals()) - if expect_running and FLAGS.start_guests_on_host_boot: + if (expect_running and FLAGS.resume_guests_state_on_host_boot)\ + or FLAGS.start_guests_on_host_boot: LOG.info(_('Rebooting instance %(inst_name)s after ' 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) diff --git a/nova/flags.py b/nova/flags.py index 23ca38b17..6c7e448ad 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -390,3 +390,5 @@ DEFINE_string('build_plan_encryption_key', None, DEFINE_bool('start_guests_on_host_boot', False, 'Whether to restart guests when the host reboots') +DEFINE_bool('resume_guests_state_on_host_boot', False, + 'Whether to start guests, that was running before the host reboot') -- cgit From 879e49c67b78d3336a24cf1af12f21258c2225fa Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 01:46:40 +0400 Subject: Estetic fix. --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index bfac3df28..a6eb1efb2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -155,7 +155,8 @@ class ComputeManager(manager.SchedulerDependentManager): db_state = instance['state'] drv_state = self._update_state(context, instance['id']) - expect_running = (db_state == power_state.RUNNING != drv_state) + expect_running = db_state == power_state.RUNNING \ + and drv_state != db_state LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, ' 'state in DB is %(db_state)s.'), locals()) -- cgit From d4842ac958bda3b446d14c7348692acc231e0041 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 02:30:20 +0400 Subject: Added ensuring filter rules for all VMs. --- nova/compute/manager.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a6eb1efb2..53390098a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -166,6 +166,11 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.info(_('Rebooting instance %(inst_name)s after ' 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) + elif drv_state == power_state.RUNNING: + try: # Hyper-V and VMWareAPI drivers will raise and exception + self.driver.ensure_filtering_rules_for_instance(instance) + except NotImplementedError: + pass def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From 8f5a6d15e671c95c6e38147ca15fb49fd672e788 Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Tue, 26 Jul 2011 02:33:39 +0400 Subject: Warn user instead of ignoring --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 53390098a..1f207e6f0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -170,7 +170,7 @@ class ComputeManager(manager.SchedulerDependentManager): try: # Hyper-V and VMWareAPI drivers will raise and exception self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - pass + LOG.warning(_('Hypervisor driver does not support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From d5b76d89d7cfc2581e2de618d33331f9267126d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 09:33:20 -0400 Subject: updating tests --- nova/tests/api/openstack/test_server_metadata.py | 55 ++++++++---------------- 1 file changed, 18 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 0431e68d2..12fc7665b 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -94,8 +94,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -104,16 +103,14 @@ class ServerMetaDataTest(unittest.TestCase): def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -123,8 +120,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -133,32 +129,28 @@ class ServerMetaDataTest(unittest.TestCase): def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key6') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key6') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_delete(self): self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/meta/key5') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -166,8 +158,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -180,8 +171,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -189,8 +179,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/100/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -200,8 +189,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -213,8 +201,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/asdf/100/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -224,9 +211,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' - req.method = 'PUT' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -234,8 +219,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" @@ -245,8 +229,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/meta/bad') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -260,8 +243,7 @@ class ServerMetaDataTest(unittest.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/servers/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -271,8 +253,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_to_many_metadata_items_on_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata_max) - req = webob.Request.blank('/v1.1/servers/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"a new key": "a new value"}' req.headers["content-type"] = "application/json" -- cgit From 0760948609ac89a43a590b36e79d691a6c79b4c3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:32:18 -0400 Subject: updating servers metadata resource --- nova/api/openstack/__init__.py | 11 +- nova/api/openstack/common.py | 4 +- nova/api/openstack/server_metadata.py | 113 ++++++++---- nova/tests/api/openstack/test_server_metadata.py | 220 ++++++++++++++++++++--- 4 files changed, 279 insertions(+), 69 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 1d14474a8..6585f1751 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,7 +164,9 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", controller=image_metadata_controller, parent_resource=dict(member_name='image', @@ -175,7 +177,14 @@ class APIRouterV11(APIRouter): action='update_all', conditions={"method": ['PUT']}) + server_metadata_controller = server_metadata.create_resource() + mapper.resource("server_meta", "metadata", - controller=server_metadata.create_resource(), + controller=server_metadata_controller, parent_resource=dict(member_name='server', collection_name='servers')) + + mapper.connect("metadata", "/servers/{server_id}/metadata", + controller=server_metadata_controller, + action='update_all', + conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..24c82035a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -247,7 +247,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): container_node = self.meta_list_to_xml(xml_doc, items) xml_doc.appendChild(container_node) self._add_xmlns(container_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def index(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) @@ -264,7 +264,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) xml_doc.appendChild(item_node) self._add_xmlns(item_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def show(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d4f42bbf5..d8f9cdbac 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import compute +from nova.api.openstack import common from nova.api.openstack import wsgi from nova import exception from nova import quota @@ -31,36 +32,37 @@ class Controller(object): super(Controller, self).__init__() def _get_metadata(self, context, server_id): - metadata = self.compute_api.get_instance_metadata(context, server_id) + try: + meta = self.compute_api.get_instance_metadata(context, server_id) + except exception.InstanceNotFound: + msg = _('Server does not exist') + raise exc.HTTPNotFound(explanation=msg) + meta_dict = {} - for key, value in metadata.iteritems(): + for key, value in meta.iteritems(): meta_dict[key] = value - return dict(metadata=meta_dict) - - def _check_body(self, body): - if body == None or body == "": - expl = _('No Request Body') - raise exc.HTTPBadRequest(explanation=expl) + return meta_dict def index(self, req, server_id): """ Returns the list of metadata for a given instance """ context = req.environ['nova.context'] - try: - return self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + return {'metadata': self._get_metadata(context, server_id)} def create(self, req, server_id, body): - self._check_body(body) + try: + metadata = body['metadata'] + except (KeyError, TypeError): + msg = _("Malformed request body") + raise exc.HTTPBadRequest(esplanation=msg) + context = req.environ['nova.context'] - metadata = body.get('metadata') + try: self.compute_api.update_or_create_instance_metadata(context, server_id, metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) except quota.QuotaError as error: @@ -69,51 +71,80 @@ class Controller(object): return body def update(self, req, server_id, id, body): - self._check_body(body) - context = req.environ['nova.context'] - if not id in body: + try: + meta_item = body['meta'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + try: + meta_value = meta_item.pop(id) + except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: + + if len(meta_item) > 0: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, meta_item) + + return {'meta': {id: meta_value}} + + def update_all(self, req, server_id, body): + try: + metadata = body['metadata'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, metadata) + + return {'metadata': metadata} + + def _set_instance_metadata(self, context, server_id, metadata): try: self.compute_api.update_or_create_instance_metadata(context, server_id, - body) + metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) + except ValueError: + msg = _("Malformed request body") + raise exc.HTTPBadRequest(explanation=msg) + except quota.QuotaError as error: self._handle_quota_error(error) - return body - def show(self, req, server_id, id): """ Return a single metadata item """ context = req.environ['nova.context'] - try: - data = self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + data = self._get_metadata(context, server_id) try: - return {id: data['metadata'][id]} + return {'meta': {id: data[id]}} except KeyError: - msg = _("metadata item %s was not found" % (id)) + msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) def delete(self, req, server_id, id): """ Deletes an existing metadata """ context = req.environ['nova.context'] + + metadata = self._get_metadata(context, server_id) + try: - self.compute_api.delete_instance_metadata(context, server_id, id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + meta_key = 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) + def _handle_quota_error(self, error): """Reraise quota errors as api-specific http exceptions.""" if error.code == "MetadataLimitExceeded": @@ -122,10 +153,16 @@ class Controller(object): def create_resource(): - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), + headers_serializer = common.MetadataHeadersSerializer() + + body_deserializers = { + 'application/xml': common.MetadataXMLDeserializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + body_serializers = { + 'application/xml': common.MetadataXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 12fc7665b..ded30a950 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -19,7 +19,7 @@ import json import stubout import unittest import webob - +from xml.dom import minidom from nova import exception from nova import flags @@ -53,11 +53,10 @@ def delete_server_metadata(context, server_id, key): def stub_server_metadata(): metadata = { - "key1": "value1", - "key2": "value2", - "key3": "value3", - "key4": "value4", - "key5": "value5"} + "key1": "value1", + "key2": "value2", + "key3": "value3", + } return metadata @@ -96,10 +95,38 @@ class ServerMetaDataTest(unittest.TestCase): return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['metadata']['key1']) + expected = { + 'metadata': { + 'key1':'value1', + 'key2':'value2', + 'key3':'value3', + }, + } + self.assertEqual(expected, res_dict) + + def test_index_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) + request = webob.Request.blank("/v1.1/servers/1/metadata") + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + self.assertEqual("application/xml", response.content_type) + + actual_metadata = minidom.parseString(response.body.replace(" ", "")) + + expected_metadata = minidom.parseString(""" + + value3 + value2 + value1 + + """.replace(" ", "").replace("\n","")) + + self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) @@ -112,24 +139,42 @@ class ServerMetaDataTest(unittest.TestCase): return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual(0, len(res_dict['metadata'])) + res_dict = json.loads(res.body) + expected = {'metadata': {}} + self.assertEqual(expected, res_dict) def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value5', res_dict['key5']) + expected = {'meta': {'key2': 'value2'}} + self.assertEqual(expected, res_dict) + + def test_show_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) + request = webob.Request.blank("/v1.1/servers/1/metadata/key2") + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + self.assertEqual("application/xml", response.content_type) + + actual_metadata = minidom.parseString(response.body.replace(" ", "")) + + expected_metadata = minidom.parseString(""" + value2 + """.replace(" ", "").replace("\n","")) + + self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -141,16 +186,27 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(404, res.status_int) def test_delete(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key2') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, res.status_int) + self.assertEqual(204, res.status_int) + self.assertEqual('', res.body) def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key5') + req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(404, res.status_int) + + def test_delete_meta_not_found(self): + self.stubs.Set(nova.db.api, 'instance_metadata_get', + return_empty_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata/key6') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -160,13 +216,38 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata') req.method = 'POST' - req.body = '{"metadata": {"key1": "value1"}}' - req.headers["content-type"] = "application/json" + req.content_type = "application/json" + expected = {"metadata": {"key1": "value1"}} + req.body = json.dumps(expected) res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) - self.assertEqual('application/json', res.headers['Content-Type']) - self.assertEqual('value1', res_dict['metadata']['key1']) + self.assertEqual(expected, res_dict) + + def test_create_xml(self): + self.stubs.Set(nova.db.api, "instance_metadata_update_or_create", + return_create_instance_metadata) + req = webob.Request.blank("/v1.1/servers/1/metadata") + req.method = "POST" + req.content_type = "application/xml" + req.accept = "application/xml" + + request_metadata = minidom.parseString(""" + + value3 + value2 + value1 + + """.replace(" ","").replace("\n","")) + + req.body = str(request_metadata.toxml()) + response = req.get_response(fakes.wsgi_app()) + + self.assertEqual(200, response.status_int) + actual_metadata = minidom.parseString(response.body) + + self.assertEqual(request_metadata.toxml(), actual_metadata.toxml()) def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', @@ -186,24 +267,105 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) + def test_update_all(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = { + 'metadata': { + 'key10': 'value10', + 'key99': 'value99', + }, + } + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual(expected, res_dict) + + def test_update_all_empty_container(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'metadata': {}} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual(expected, res_dict) + + def test_update_all_malformed_container(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'meta': {}} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_all_malformed_data(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata') + req.method = 'PUT' + req.content_type = "application/json" + expected = {'metadata': ['asdf']} + req.body = json.dumps(expected) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_all_nonexistant_server(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) + req = webob.Request.blank('/v1.1/servers/100/metadata') + req.method = 'PUT' + req.content_type = "application/json" + req.body = json.dumps({'metadata': {'key10': 'value10'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(404, res.status_int) + def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) self.assertEqual('application/json', res.headers['Content-Type']) res_dict = json.loads(res.body) - self.assertEqual('value1', res_dict['key1']) + expected = {'meta': {'key1': 'value1'}} + self.assertEqual(expected, res_dict) + + def test_update_item_xml(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/metadata/key9') + req.method = 'PUT' + req.accept = "application/json" + req.content_type = "application/xml" + req.body = """ + value9 + """ + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) + res_dict = json.loads(res.body) + expected = {'meta': {'key9': 'value9'}} + self.assertEqual(expected, res_dict) def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta":{"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -212,6 +374,7 @@ class ServerMetaDataTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -221,7 +384,7 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"key1": "value1", "key2": "value2"}' + req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -231,7 +394,7 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/metadata/bad') req.method = 'PUT' - req.body = '{"key1": "value1"}' + req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) @@ -255,7 +418,8 @@ class ServerMetaDataTest(unittest.TestCase): return_create_instance_metadata_max) req = webob.Request.blank('/v1.1/servers/1/metadata/key1') req.method = 'PUT' - req.body = '{"a new key": "a new value"}' + req.body = '{"meta": {"a new key": "a new value"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) + -- cgit From 4add644dbd1650a3e83e715c6d6a38c4114e4d06 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 11:39:34 -0400 Subject: Moved server/actions tests to test_server_actions.py --- nova/tests/api/openstack/test_server_actions.py | 628 ++++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 492 ------------------- 2 files changed, 628 insertions(+), 492 deletions(-) create mode 100644 nova/tests/api/openstack/test_server_actions.py (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py new file mode 100644 index 000000000..97c108068 --- /dev/null +++ b/nova/tests/api/openstack/test_server_actions.py @@ -0,0 +1,628 @@ +import base64 +import json +import unittest +from xml.dom import minidom + +import stubout +import webob + +from nova import context +from nova import db +from nova import utils +from nova.api.openstack import create_instance_helper +from nova.compute import instance_types +from nova.compute import power_state +import nova.db.api +from nova import test +from nova.tests.api.openstack import common +from nova.tests.api.openstack import fakes + + +def return_server_by_id(context, id): + return _get_instance() + + +def instance_update(context, instance_id, kwargs): + return _get_instance() + + +def return_server_with_power_state(power_state): + def _return_server(context, id): + instance = _get_instance() + instance['state'] = power_state + return instance + return _return_server + + +def return_server_with_uuid_and_power_state(power_state): + def _return_server(context, id): + return return_server_with_power_state(power_state) + return _return_server + + +class MockSetAdminPassword(object): + def __init__(self): + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.instance_id = instance_id + self.password = password + + +def _get_instance(): + instance = { + "id": 1, + "created_at": "2010-10-10T12:00:00Z", + "updated_at": "2010-11-11T11:00:00Z", + "admin_pass": "", + "user_id": "", + "project_id": "", + "image_ref": "5", + "kernel_id": "", + "ramdisk_id": "", + "launch_index": 0, + "key_name": "", + "key_data": "", + "state": 0, + "state_description": "", + "memory_mb": 0, + "vcpus": 0, + "local_gb": 0, + "hostname": "", + "host": "", + "instance_type": { + "flavorid": 1, + }, + "user_data": "", + "reservation_id": "", + "mac_address": "", + "scheduled_at": utils.utcnow(), + "launched_at": utils.utcnow(), + "terminated_at": utils.utcnow(), + "availability_zone": "", + "display_name": "test_server", + "display_description": "", + "locked": False, + "metadata": [], + #"address": , + #"floating_ips": [{"address":ip} for ip in public_addresses]} + "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} + + return instance + + +class ServerActionsTest(test.TestCase): + + def setUp(self): + self.maxDiff = None + super(ServerActionsTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + self.stubs.Set(nova.db.api, 'instance_update', instance_update) + + self.webreq = common.webob_factory('/v1.0/servers') + + def tearDown(self): + self.stubs.UnsetAll() + + def test_server_actions(self): + req = webob.Request.blank("/v1.0/servers/1/actions") + req.method = "GET" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_server_change_password(self): + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_change_password_xml(self): + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/xml' + req.body = '' +# res = req.get_response(fakes.wsgi_app()) +# self.assertEqual(res.status_int, 501) + + def test_server_change_password_not_a_string_v1_1(self): + body = {'changePassword': {'adminPass': 1234}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_reboot(self): + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + + def test_server_rebuild_accepted(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(res.body, "") + + def test_server_rebuild_rejected_when_building(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + state = power_state.BUILDING + new_return_server = return_server_with_power_state(state) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + self.stubs.Set(nova.db, 'instance_get_by_uuid', + return_server_with_uuid_and_power_state(state)) + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + + def test_server_rebuild_bad_entity(self): + body = { + "rebuild": { + }, + } + + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + def test_resize_bad_flavor_fails(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 422) + self.assertEqual(self.resize_called, False) + + def test_resize_raises_fails(self): + req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) + + def resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 500) + + def test_resized_server_has_correct_status(self): + req = self.webreq('/1', 'GET') + + def fake_migration_get(*args): + return {} + + self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', + fake_migration_get) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + body = json.loads(res.body) + self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') + + def test_confirm_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) + + self.resize_called = False + + def confirm_resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', + confirm_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) + self.assertEqual(self.resize_called, True) + + def test_confirm_resize_server_fails(self): + req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) + + def confirm_resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', + confirm_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_revert_resize_server(self): + req = self.webreq('/1/action', 'POST', dict(revertResize=None)) + + self.resize_called = False + + def revert_resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'revert_resize', + revert_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + def test_revert_resize_server_fails(self): + req = self.webreq('/1/action', 'POST', dict(revertResize=None)) + + def revert_resize_mock(*args): + raise Exception('hurr durr') + + self.stubs.Set(nova.compute.api.API, 'revert_resize', + revert_resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_migrate_server(self): + """This is basically the same as resize, only we provide the `migrate` + attribute in the body's dict. + """ + req = self.webreq('/1/action', 'POST', dict(migrate=None)) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + +class ServerActionsTestV11(test.TestCase): + + def setUp(self): + self.maxDiff = None + super(ServerActionsTestV11, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + self.stubs.Set(nova.db.api, 'instance_update', instance_update) + + fakes.stub_out_glance(self.stubs) + fakes.stub_out_compute_api_snapshot(self.stubs) + service_class = 'nova.image.glance.GlanceImageService' + self.service = utils.import_object(service_class) + self.context = context.RequestContext(1, None) + self.service.delete_all() + self.sent_to_glance = {} + fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) + + def tearDown(self): + self.stubs.UnsetAll() + + def test_server_change_password_v1_1(self): + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + + def test_server_change_password_bad_request_v1_1(self): + body = {'changePassword': {'pass': '12345'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_empty_string_v1_1(self): + body = {'changePassword': {'adminPass': ''}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_none_v1_1(self): + body = {'changePassword': {'adminPass': None}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_accepted_minimum_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_rejected_when_building_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + state = power_state.BUILDING + new_return_server = return_server_with_power_state(state) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + self.stubs.Set(nova.db, 'instance_get_by_uuid', + return_server_with_uuid_and_power_state(state)) + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 409) + + def test_server_rebuild_accepted_with_metadata_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": { + "new": "metadata", + }, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "metadata": "stack", + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_bad_entity_v1_1(self): + body = { + "rebuild": { + "imageId": 2, + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_bad_personality_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": "INVALID b64", + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_rebuild_personality_v1_1(self): + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "personality": [{ + "path": "/path/to/file", + "contents": base64.b64encode("Test String"), + }] + }, + } + + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_resize_server_v11(self): + + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict(flavorRef="http://localhost/3")) + req.body = json.dumps(body_dict) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + + def test_create_image_v1_1(self): + body = { + 'createImage': { + 'name': 'Snapshot 1', + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + location = response.headers['Location'] + self.assertEqual('http://localhost/v1.1/images/123', location) + + def test_create_image_v1_1_with_metadata(self): + body = { + 'createImage': { + 'name': 'Snapshot 1', + 'metadata': {'key': 'asdf'}, + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(202, response.status_int) + location = response.headers['Location'] + self.assertEqual('http://localhost/v1.1/images/123', location) + + def test_create_image_v1_1_no_name(self): + body = { + 'createImage': {}, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_image_v1_1_bad_metadata(self): + body = { + 'createImage': { + 'name': 'geoff', + 'metadata': 'henry', + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + +class TestServerActionXMLDeserializer(test.TestCase): + + def setUp(self): + self.deserializer = create_instance_helper.ServerXMLDeserializer() + + def tearDown(self): + pass + + def test_create_image(self): + serial_request = """ +""" + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "createImage": { + "name": "new-server-test", + "metadata": {}, + }, + } + self.assertEquals(request['body'], expected) + + def test_create_image_with_metadata(self): + serial_request = """ + + + value1 + +""" + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "createImage": { + "name": "new-server-test", + "metadata": {"key1": "value1"}, + }, + } + self.assertEquals(request['body'], expected) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9d2c7b73f..b68183429 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1766,262 +1766,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) - def test_server_actions(self): - req = webob.Request.blank("/v1.0/servers/1/actions") - req.method = "GET" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 404) - - def test_server_change_password(self): - body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 501) - - def test_server_change_password_xml(self): - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/xml' - req.body = '' -# res = req.get_response(fakes.wsgi_app()) -# self.assertEqual(res.status_int, 501) - - def test_server_change_password_v1_1(self): - mock_method = MockSetAdminPassword() - self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) - body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(mock_method.instance_id, '1') - self.assertEqual(mock_method.password, '1234pass') - - def test_server_change_password_bad_request_v1_1(self): - body = {'changePassword': {'pass': '12345'}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_empty_string_v1_1(self): - body = {'changePassword': {'adminPass': ''}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_none_v1_1(self): - body = {'changePassword': {'adminPass': None}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_change_password_not_a_string_v1_1(self): - body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_reboot(self): - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - - def test_server_rebuild_accepted(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(res.body, "") - - def test_server_rebuild_rejected_when_building(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 409) - - def test_server_rebuild_bad_entity(self): - body = { - "rebuild": { - }, - } - - req = webob.Request.blank('/v1.0/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_accepted_minimum_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - - def test_server_rebuild_rejected_when_building_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - }, - } - - state = power_state.BUILDING - new_return_server = return_server_with_power_state(state) - self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_with_uuid_and_power_state(state)) - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 409) - - def test_server_rebuild_accepted_with_metadata_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "metadata": { - "new": "metadata", - }, - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - - def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "metadata": "stack", - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_bad_entity_v1_1(self): - body = { - "rebuild": { - "imageId": 2, - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_bad_personality_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "personality": [{ - "path": "/path/to/file", - "contents": "INVALID b64", - }] - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_server_rebuild_personality_v1_1(self): - body = { - "rebuild": { - "imageRef": "http://localhost/images/2", - "personality": [{ - "path": "/path/to/file", - "contents": base64.b64encode("Test String"), - }] - }, - } - - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'DELETE' @@ -2089,147 +1833,6 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 204) self.assertEqual(self.server_delete_called, True) - def test_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_server_v11(self): - - req = webob.Request.blank('/v1.1/servers/1/action') - req.content_type = 'application/json' - req.method = 'POST' - body_dict = dict(resize=dict(flavorRef="http://localhost/3")) - req.body = json.dumps(body_dict) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_resize_bad_flavor_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 422) - self.assertEqual(self.resize_called, False) - - def test_resize_raises_fails(self): - req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3))) - - def resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 500) - - def test_resized_server_has_correct_status(self): - req = self.webreq('/1', 'GET') - - def fake_migration_get(*args): - return {} - - self.stubs.Set(nova.db, 'migration_get_by_instance_and_status', - fake_migration_get) - res = req.get_response(fakes.wsgi_app()) - body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') - - def test_confirm_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - self.resize_called = False - - def confirm_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 204) - self.assertEqual(self.resize_called, True) - - def test_confirm_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) - - def confirm_resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'confirm_resize', - confirm_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_revert_resize_server(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - self.resize_called = False - - def revert_resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - - def test_revert_resize_server_fails(self): - req = self.webreq('/1/action', 'POST', dict(revertResize=None)) - - def revert_resize_mock(*args): - raise Exception('hurr durr') - - self.stubs.Set(nova.compute.api.API, 'revert_resize', - revert_resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - - def test_migrate_server(self): - """This is basically the same as resize, only we provide the `migrate` - attribute in the body's dict. - """ - req = self.webreq('/1/action', 'POST', dict(migrate=None)) - - self.resize_called = False - - def resize_mock(*args): - self.resize_called = True - - self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) - - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 202) - self.assertEqual(self.resize_called, True) - def test_shutdown_status(self): new_server = return_server_with_power_state(power_state.SHUTDOWN) self.stubs.Set(nova.db.api, 'instance_get', new_server) @@ -2248,101 +1851,6 @@ class ServersTest(test.TestCase): res_dict = json.loads(res.body) self.assertEqual(res_dict['server']['status'], 'SHUTOFF') - def test_create_image_v1_1(self): - body = { - 'createImage': { - 'name': 'Snapshot 1', - }, - } - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(202, response.status_int) - location = response.headers['Location'] - self.assertEqual('http://localhost/v1.1/images/123', location) - - def test_create_image_v1_1_with_metadata(self): - body = { - 'createImage': { - 'name': 'Snapshot 1', - 'metadata': {'key': 'asdf'}, - }, - } - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(202, response.status_int) - location = response.headers['Location'] - self.assertEqual('http://localhost/v1.1/images/123', location) - - def test_create_image_v1_1_no_name(self): - body = { - 'createImage': {}, - } - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_create_image_v1_1_bad_metadata(self): - body = { - 'createImage': { - 'name': 'geoff', - 'metadata': 'henry', - }, - } - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - response = req.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - -class TestServerActionXMLDeserializer(test.TestCase): - - def setUp(self): - self.deserializer = create_instance_helper.ServerXMLDeserializer() - - def tearDown(self): - pass - - def test_create_image(self): - serial_request = """ -""" - request = self.deserializer.deserialize(serial_request, 'action') - expected = { - "createImage": { - "name": "new-server-test", - "metadata": {}, - }, - } - self.assertEquals(request['body'], expected) - - def test_create_image_with_metadata(self): - serial_request = """ - - - value1 - -""" - request = self.deserializer.deserialize(serial_request, 'action') - expected = { - "createImage": { - "name": "new-server-test", - "metadata": {"key1": "value1"}, - }, - } - self.assertEquals(request['body'], expected) - class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase): -- cgit From 154129acf1ecbdd97e5bb8558598a9b24eb8559f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 11:43:20 -0400 Subject: Cleaned up test_servers --- nova/tests/api/openstack/test_servers.py | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b68183429..c33d6b367 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -97,12 +97,6 @@ def return_server_with_power_state(power_state): return _return_server -def return_server_with_uuid_and_power_state(power_state): - def _return_server(context, id): - return stub_instance(id, uuid=FAKE_UUID, power_state=power_state) - return _return_server - - def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -264,19 +258,8 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, "get_diagnostics", fake_compute_api) self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api) - fakes.stub_out_glance(self.stubs) - fakes.stub_out_compute_api_snapshot(self.stubs) - service_class = 'nova.image.glance.GlanceImageService' - self.service = utils.import_object(service_class) - self.context = context.RequestContext(1, None) - self.service.delete_all() - self.sent_to_glance = {} - fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) - self.allow_admin = FLAGS.allow_admin_api - self.webreq = common.webob_factory('/v1.0/servers') - def tearDown(self): self.stubs.UnsetAll() FLAGS.allow_admin_api = self.allow_admin -- cgit From b6ee05e9575769039aca2c65c2a761c14562e7e0 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:45:22 -0400 Subject: updating common metadata xml serializer tests --- nova/tests/api/openstack/test_common.py | 60 +++++++++++---------------------- 1 file changed, 20 insertions(+), 40 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index 0b76841f0..ee96fea7e 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -323,14 +323,10 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - four - - - two - + four + two - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -346,11 +342,9 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - None - + None - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -366,11 +360,9 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(u""" - - Jos\xe9 - + Jos\xe9 - """.encode("UTF-8").replace(" ", "")) + """.encode("UTF-8").replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -385,10 +377,9 @@ class MetadataXMLSerializationTest(test.TestCase): actual = minidom.parseString(output.replace(" ", "")) expected = minidom.parseString(""" - - two - - """.replace(" ", "")) + two + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -405,14 +396,10 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - value6 - - - value4 - + value6 + value4 - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -427,10 +414,9 @@ class MetadataXMLSerializationTest(test.TestCase): actual = minidom.parseString(output.replace(" ", "")) expected = minidom.parseString(""" - - two - - """.replace(" ", "")) + two + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -448,17 +434,11 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" - - value2 - - - value9 - - - value1 - + value2 + value9 + value1 - """.replace(" ", "")) + """.replace(" ", "").replace("\n","")) self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From 572847f9eb43ce23190566439118547ae6d6a992 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:53:09 -0400 Subject: pep8 --- nova/api/openstack/server_metadata.py | 6 +++--- nova/tests/api/openstack/test_common.py | 14 +++++++------- nova/tests/api/openstack/test_server_metadata.py | 13 ++++++------- 3 files changed, 16 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d8f9cdbac..f25d36535 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -72,13 +72,13 @@ class Controller(object): def update(self, req, server_id, id, body): try: - meta_item = body['meta'] + meta_item = body['meta'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) try: - meta_value = meta_item.pop(id) + meta_value = meta_item.pop(id) except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -94,7 +94,7 @@ class Controller(object): def update_all(self, req, server_id, body): try: - metadata = body['metadata'] + metadata = body['metadata'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index ee96fea7e..5a6e43579 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -326,7 +326,7 @@ class MetadataXMLSerializationTest(test.TestCase): four two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -344,7 +344,7 @@ class MetadataXMLSerializationTest(test.TestCase): None - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -362,7 +362,7 @@ class MetadataXMLSerializationTest(test.TestCase): Jos\xe9 - """.encode("UTF-8").replace(" ", "").replace("\n","")) + """.encode("UTF-8").replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -379,7 +379,7 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -399,7 +399,7 @@ class MetadataXMLSerializationTest(test.TestCase): value6 value4 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -416,7 +416,7 @@ class MetadataXMLSerializationTest(test.TestCase): expected = minidom.parseString(""" two - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -438,7 +438,7 @@ class MetadataXMLSerializationTest(test.TestCase): value9 value1 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected.toxml(), actual.toxml()) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index ded30a950..2f432433d 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -100,9 +100,9 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('application/json', res.headers['Content-Type']) expected = { 'metadata': { - 'key1':'value1', - 'key2':'value2', - 'key3':'value3', + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', }, } self.assertEqual(expected, res_dict) @@ -124,7 +124,7 @@ class ServerMetaDataTest(unittest.TestCase): value2 value1 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) @@ -168,7 +168,7 @@ class ServerMetaDataTest(unittest.TestCase): expected_metadata = minidom.parseString(""" value2 - """.replace(" ", "").replace("\n","")) + """.replace(" ", "").replace("\n", "")) self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml()) @@ -239,7 +239,7 @@ class ServerMetaDataTest(unittest.TestCase): value2 value1 - """.replace(" ","").replace("\n","")) + """.replace(" ", "").replace("\n", "")) req.body = str(request_metadata.toxml()) response = req.get_response(fakes.wsgi_app()) @@ -422,4 +422,3 @@ class ServerMetaDataTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) - -- cgit From f9ff78a5ac5f83d789334c36bebfce62af0ea406 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 17:20:42 -0400 Subject: refactoring MetadataXMLDeserializer in wsgi/common --- nova/api/openstack/common.py | 12 +++++++++++- nova/api/openstack/create_instance_helper.py | 7 +++++-- nova/api/openstack/wsgi.py | 13 ------------- 3 files changed, 16 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 24c82035a..a99951764 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -196,7 +196,17 @@ def get_version_from_href(href): return version -class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): +class MetadataXMLDeserializer(wsgi.XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata def _extract_metadata_container(self, datastring): dom = minidom.parseString(datastring) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..70532cf79 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,6 +28,7 @@ from nova import quota from nova import utils from nova.compute import instance_types +from nova.api.openstack import common from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -285,7 +286,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): +class ServerXMLDeserializer(wsgi.XMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -293,6 +294,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): and personality attributes """ + metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) @@ -307,7 +310,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - metadata = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..d10424d79 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -161,19 +161,6 @@ class XMLDeserializer(TextDeserializer): return {'body': self._from_xml(datastring)} -class MetadataXMLDeserializer(XMLDeserializer): - - def extract_metadata(self, metadata_node): - """Marshal the metadata attribute of a parsed request""" - if metadata_node is None: - return None - metadata = {} - for meta_node in self.find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self.extract_text(meta_node) - return metadata - - class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" -- cgit From 92c8d269a13917de397c1d0ce9fecfaa36195ce9 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 28 Jul 2011 10:23:44 -0400 Subject: Removed v1_1 from individual tests --- nova/tests/api/openstack/test_server_actions.py | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 97c108068..5c23d6d9d 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -350,7 +350,7 @@ class ServerActionsTestV11(test.TestCase): def tearDown(self): self.stubs.UnsetAll() - def test_server_change_password_v1_1(self): + def test_server_change_password(self): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) body = {'changePassword': {'adminPass': '1234pass'}} @@ -363,7 +363,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') - def test_server_change_password_bad_request_v1_1(self): + def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -372,7 +372,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_change_password_empty_string_v1_1(self): + def test_server_change_password_empty_string(self): body = {'changePassword': {'adminPass': ''}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -381,7 +381,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_change_password_none_v1_1(self): + def test_server_change_password_none(self): body = {'changePassword': {'adminPass': None}} req = webob.Request.blank('/v1.1/servers/1/action') req.method = 'POST' @@ -390,7 +390,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_accepted_minimum_v1_1(self): + def test_server_rebuild_accepted_minimum(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -405,7 +405,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_rejected_when_building_v1_1(self): + def test_server_rebuild_rejected_when_building(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -426,7 +426,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 409) - def test_server_rebuild_accepted_with_metadata_v1_1(self): + def test_server_rebuild_accepted_with_metadata(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -444,7 +444,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_server_rebuild_accepted_with_bad_metadata_v1_1(self): + def test_server_rebuild_accepted_with_bad_metadata(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -460,7 +460,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_entity_v1_1(self): + def test_server_rebuild_bad_entity(self): body = { "rebuild": { "imageId": 2, @@ -475,7 +475,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_bad_personality_v1_1(self): + def test_server_rebuild_bad_personality(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -494,7 +494,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) - def test_server_rebuild_personality_v1_1(self): + def test_server_rebuild_personality(self): body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -513,7 +513,7 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_resize_server_v11(self): + def test_resize_server(self): req = webob.Request.blank('/v1.1/servers/1/action') req.content_type = 'application/json' @@ -532,7 +532,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) - def test_create_image_v1_1(self): + def test_create_image(self): body = { 'createImage': { 'name': 'Snapshot 1', @@ -547,7 +547,7 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) - def test_create_image_v1_1_with_metadata(self): + def test_create_image_with_metadata(self): body = { 'createImage': { 'name': 'Snapshot 1', @@ -563,7 +563,7 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) - def test_create_image_v1_1_no_name(self): + def test_create_image_no_name(self): body = { 'createImage': {}, } @@ -574,7 +574,7 @@ class ServerActionsTestV11(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) - def test_create_image_v1_1_bad_metadata(self): + def test_create_image_bad_metadata(self): body = { 'createImage': { 'name': 'geoff', -- cgit From cd065f6669a666387c8f9efb5c0fff7eaf94521c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 28 Jul 2011 10:31:06 -0400 Subject: moved test --- nova/tests/api/openstack/test_server_actions.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 5c23d6d9d..1651b1645 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -132,15 +132,6 @@ class ServerActionsTest(test.TestCase): # res = req.get_response(fakes.wsgi_app()) # self.assertEqual(res.status_int, 501) - def test_server_change_password_not_a_string_v1_1(self): - body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, @@ -363,6 +354,15 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_not_a_string(self): + body = {'changePassword': {'adminPass': 1234}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} req = webob.Request.blank('/v1.1/servers/1/action') -- cgit From f91413bec1e6698935e00c323befa0655dea1ab1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 29 Jul 2011 10:22:50 -0400 Subject: Fixed changes missed in merge --- nova/tests/api/openstack/test_server_actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 1651b1645..0bc7e4cf3 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -53,8 +53,8 @@ class MockSetAdminPassword(object): def _get_instance(): instance = { "id": 1, - "created_at": "2010-10-10T12:00:00Z", - "updated_at": "2010-11-11T11:00:00Z", + "created_at": "2010-10-10 12:00:00", + "updated_at": "2010-11-11 11:00:00", "admin_pass": "", "user_id": "", "project_id": "", -- 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 +++++++++++++++ nova/compute/api.py | 5 +++++ nova/compute/manager.py | 8 +++++++- nova/tests/test_hosts.py | 9 +++++++++ nova/virt/driver.py | 4 ++++ nova/virt/fake.py | 4 ++++ nova/virt/hyperv.py | 4 ++++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/vmwareapi_conn.py | 4 ++++ nova/virt/xenapi/vmops.py | 8 ++++++++ nova/virt/xenapi_conn.py | 4 ++++ 11 files changed, 68 insertions(+), 1 deletion(-) (limited to 'nova') 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): diff --git a/nova/compute/api.py b/nova/compute/api.py index 8f7b3c3ef..bd17fdf31 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -998,6 +998,11 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) + def set_host_powerstate(self, context, host, state): + """Reboots or shuts down the host.""" + return self._call_compute_message("set_host_powerstate", context, + instance_id=None, host=host, params={"state": state}) + @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..d0f9a81f4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 +: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -927,6 +927,12 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, result)) + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) + def set_host_powerstate(self, context, instance_id=None, host=None, + state=None): + """Reboots or shuts down the host.""" + return self.driver.set_host_powerstate(host, state) + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def set_host_enabled(self, context, instance_id=None, host=None, enabled=None): diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 548f81f8b..ad057f429 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,6 +48,13 @@ def stub_set_host_enabled(context, host, enabled): return status +def stub_set_host_powerstate(context, host, state): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + return state if host == "host_c1" else "running" + + class FakeRequest(object): environ = {"nova.context": context.get_admin_context()} @@ -62,6 +69,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'set_host_powerstate', + stub_set_host_powerstate) def test_list_hosts(self): """Verify that the compute hosts are returned.""" diff --git a/nova/virt/driver.py b/nova/virt/driver.py index b219fb2cb..bbb17480d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -251,6 +251,10 @@ class ComputeDriver(object): """Poll for rescued instances""" raise NotImplementedError() + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + raise NotImplementedError() + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 26bc421c0..6dc6552d7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -515,6 +515,10 @@ class FakeConnection(driver.ComputeDriver): """Return fake Host Status of ram, disk, network.""" return self.host_status + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index c26fe108b..119def38b 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -498,6 +498,10 @@ class HyperVConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 17c328a83..ae1a16d44 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1583,6 +1583,10 @@ class LibvirtConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index ce57847b2..af547821f 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -190,6 +190,10 @@ class VMWareESXConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + pass + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7e02e1def..8e57042f9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1023,6 +1023,14 @@ class VMOps(object): # TODO: implement this! return 'http://fakeajaxconsole/fake_url' + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + args = {"state": json.dumps(state)} + methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} + json_resp = self._call_xenhost(methods[state], args) + resp = json.loads(json_resp) + return resp["powerstate"] + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" args = {"enabled": json.dumps(enabled)} diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index cc18ed83c..5ac837a17 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -334,6 +334,10 @@ class XenAPIConnection(driver.ComputeDriver): True, run the update first.""" return self.HostState.get_host_stats(refresh=refresh) + def set_host_powerstate(self, host, state): + """Reboots or shuts down the host.""" + return self._vmops.set_host_powerstate(host, state) + def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) -- cgit From 883095f1e69d70173339ccba64f2b2a8a1d48a5f Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Mon, 1 Aug 2011 20:45:30 -0500 Subject: Added @test.skip_unless and @test.skip_if functionality. Also created nova/tests/test_skip_examples.py to show the skip cases usage. --- nova/test.py | 31 +++++++++++++++++++++++++++++++ nova/tests/test_skip_examples.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 nova/tests/test_skip_examples.py (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 549aa6fcf..afc118100 100644 --- a/nova/test.py +++ b/nova/test.py @@ -67,6 +67,37 @@ class skip_test(object): _skipper.__doc__ = func.__doc__ return _skipper +class skip_if(object): + """Decorator that skips a test if contition is true.""" + def __init__(self, condition, msg): + self.condition = condition + self.message = msg + + def __call__(self, func): + def _skipper(*args, **kw): + """Wrapped skipper function.""" + if self.condition: + raise nose.SkipTest(self.message) + _skipper.__name__ = func.__name__ + _skipper.__doc__ = func.__doc__ + return _skipper + + +class skip_unless(object): + """Decorator that skips a test if condition is false.""" + def __init__(self, condition, msg): + self.condition = condition + self.message = msg + + def __call__(self, func): + def _skipper(*args, **kw): + """Wrapped skipper function.""" + if not self.condition: + raise nose.SkipTest(self.message) + _skipper.__name__ = func.__name__ + _skipper.__doc__ = func.__doc__ + return _skipper + def skip_if_fake(func): """Decorator that skips a test if running in fake mode.""" diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py new file mode 100644 index 000000000..dfbaac74b --- /dev/null +++ b/nova/tests/test_skip_examples.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 + +from nova import test + + +class ExampleSkipTestCase(test.TestCase): + @test.skip_test("testing skipping") + def test_skip_test(self): + x = 1 + + @test.skip_if(os.getenv("USER"), + "Skiping -- Environment variable USER exists") + def test_skip_if_env_user_exists(self): + x = 1 + + @test.skip_unless(os.getenv("BLAH"), + "Skipping -- Environment variable BLAH does not exist") + def test_skip_unless_env_blah_exists(self): + x = 1 -- cgit From 695afaffaa4de359b306280c252f8f40a3bab5a7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 2 Aug 2011 11:48:01 -0400 Subject: cleanup --- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index f25d36535..b0b014f86 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -53,7 +53,7 @@ class Controller(object): metadata = body['metadata'] except (KeyError, TypeError): msg = _("Malformed request body") - raise exc.HTTPBadRequest(esplanation=msg) + raise exc.HTTPBadRequest(explanation=msg) context = req.environ['nova.context'] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1453424ce..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -19,7 +19,6 @@ import traceback from webob import exc from xml.dom import minidom import webob -from xml.dom import minidom from nova import compute from nova import exception -- cgit From 113fe431d61899b8402181d1016ab29479330636 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 2 Aug 2011 13:34:29 -0500 Subject: Get instance by UUID instead of id. --- nova/compute/manager.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..c7c88a1fb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -648,37 +648,38 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def rescue_instance(self, context, instance_id): + def rescue_instance(self, context, instance_uuid): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: rescuing'), instance_id, context=context) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + LOG.audit(_('instance %s: rescuing'), instance_uuid, context=context) self.db.instance_set_state(context, - instance_id, + instance_uuid, power_state.NOSTATE, 'rescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) + self, context, instance_uuid, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.rescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_id) + self._update_state(context, instance_uuid) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def unrescue_instance(self, context, instance_id): + def unrescue_instance(self, context, instance_uuid): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) - LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + LOG.audit(_('instance %s: unrescuing'), instance_uuid, + context=context) self.db.instance_set_state(context, - instance_id, + instance_uuid, power_state.NOSTATE, 'unrescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) + self, context, instance_uuid, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.unrescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_id) + self._update_state(context, instance_uuid) @staticmethod def _update_state_callback(self, context, instance_id, result): -- 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 ++--- nova/compute/manager.py | 2 +- nova/tests/test_hosts.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'nova') 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) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d0f9a81f4..cd05f3f24 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -: tabstop=4 shiftwidth=4 softtabstop=4 +#: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index ad057f429..9f54d5ec7 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -52,7 +52,7 @@ def stub_set_host_powerstate(context, host, state): # We'll simulate success and failure by assuming # that 'host_c1' always succeeds, and 'host_c2' # always fails - return state if host == "host_c1" else "running" + return state if host == "host_c1" else "running" class FakeRequest(object): -- cgit From 4887331a0d972b4d9e1fbd365c36edf1d7b076ec Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 15:10:09 -0400 Subject: fix pylint errors --- nova/tests/scheduler/test_scheduler.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 6a56a57db..a515636a3 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -51,10 +51,10 @@ FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' class TestDriver(driver.Scheduler): """Scheduler Driver for Tests""" - def schedule(context, topic, *args, **kwargs): + def schedule(self, context, topic, *args, **kwargs): return 'fallback_host' - def schedule_named_method(context, topic, num): + def schedule_named_method(self, context, topic, num): return 'named_host' @@ -301,7 +301,7 @@ class SimpleDriverTestCase(test.TestCase): db.compute_node_create(self.context, dic) return db.service_get(self.context, s_ref['id']) - def test_doesnt_report_disabled_hosts_as_up(self): + def test_doesnt_report_disabled_hosts_as_up_no_queue(self): """Ensures driver doesn't find hosts before they are enabled""" # NOTE(vish): constructing service without create method # because we are going to use it without queue @@ -324,7 +324,7 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() - def test_reports_enabled_hosts_as_up(self): + def test_reports_enabled_hosts_as_up_no_queue(self): """Ensures driver can find the hosts that are up""" # NOTE(vish): constructing service without create method # because we are going to use it without queue @@ -343,7 +343,7 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() - def test_least_busy_host_gets_instance(self): + def test_least_busy_host_gets_instance_no_queue(self): """Ensures the host with less cores gets the next one""" compute1 = service.Service('host1', 'nova-compute', @@ -366,7 +366,7 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() - def test_specific_host_gets_instance(self): + def test_specific_host_gets_instance_no_queue(self): """Ensures if you set availability_zone it launches on that zone""" compute1 = service.Service('host1', 'nova-compute', @@ -389,7 +389,7 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() - def test_wont_sechedule_if_specified_host_is_down(self): + def test_wont_sechedule_if_specified_host_is_down_no_queue(self): compute1 = service.Service('host1', 'nova-compute', 'compute', @@ -408,7 +408,7 @@ class SimpleDriverTestCase(test.TestCase): db.instance_destroy(self.context, instance_id2) compute1.kill() - def test_will_schedule_on_disabled_host_if_specified(self): + def test_will_schedule_on_disabled_host_if_specified_no_queue(self): compute1 = service.Service('host1', 'nova-compute', 'compute', @@ -423,7 +423,7 @@ class SimpleDriverTestCase(test.TestCase): db.instance_destroy(self.context, instance_id2) compute1.kill() - def test_too_many_cores(self): + def test_too_many_cores_no_queue(self): """Ensures we don't go over max cores""" compute1 = service.Service('host1', 'nova-compute', @@ -456,7 +456,7 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() - def test_least_busy_host_gets_volume(self): + def test_least_busy_host_gets_volume_no_queue(self): """Ensures the host with less gigabytes gets the next one""" volume1 = service.Service('host1', 'nova-volume', @@ -477,7 +477,7 @@ class SimpleDriverTestCase(test.TestCase): volume1.delete_volume(self.context, volume_id1) db.volume_destroy(self.context, volume_id2) - def test_doesnt_report_disabled_hosts_as_up(self): + def test_doesnt_report_disabled_hosts_as_up2(self): """Ensures driver doesn't find hosts before they are enabled""" compute1 = self.start_service('compute', host='host1') compute2 = self.start_service('compute', host='host2') @@ -993,7 +993,7 @@ class ZoneRedirectTest(test.TestCase): decorator = FakeRerouteCompute("foo", id_to_return=FAKE_UUID_NOT_FOUND) try: result = decorator(go_boom)(None, None, 1) - self.assertFail(_("Should have rerouted.")) + self.fail(_("Should have rerouted.")) except api.RedirectResult, e: self.assertEquals(e.results['magic'], 'found me') @@ -1081,10 +1081,10 @@ class DynamicNovaClientTest(test.TestCase): class FakeZonesProxy(object): - def do_something(*args, **kwargs): + def do_something(self, *args, **kwargs): return 42 - def raises_exception(*args, **kwargs): + def raises_exception(self, *args, **kwargs): raise Exception('testing') -- cgit From 378571665538fb5c4667a9d068678378ddf8526f Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 15:19:00 -0400 Subject: fix pylint errors --- nova/db/sqlalchemy/api.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 36614df1f..ad935fa44 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3202,9 +3202,8 @@ def instance_metadata_update_or_create(context, instance_id, metadata): meta_ref = None for key, value in metadata.iteritems(): try: - meta_ref = instance_metadata_get_item(context, instance_id, key, - session) - except: + meta_ref = instance_metadata_get_item(context, instance_id, key) + except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() meta_ref.update({"key": key, "value": value, "instance_id": instance_id, @@ -3322,11 +3321,9 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id, spec_ref = None for key, value in specs.iteritems(): try: - spec_ref = instance_type_extra_specs_get_item(context, - instance_type_id, - key, - session) - except: + spec_ref = instance_type_extra_specs_get_item( + context, instance_type_id, key) + except exception.InstanceTypeExtraSpecsNotFound, e: spec_ref = models.InstanceTypeExtraSpecs() spec_ref.update({"key": key, "value": value, "instance_type_id": instance_type_id, -- cgit From d1891d2dd18a14535ec22a0363fd8234a01dbb8c Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 15:30:27 -0400 Subject: remove unused parameter --- nova/virt/hyperv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index c26fe108b..315f635c7 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -373,7 +373,7 @@ class HyperVConnection(driver.ComputeDriver): raise exception.InstanceNotFound(instance_id=instance.id) self._set_vm_state(instance.name, 'Reboot') - def destroy(self, instance, network_info): + def destroy(self, instance): """Destroy the VM. Also destroy the associated VHD disk files""" LOG.debug(_("Got request to destroy vm %s"), instance.name) vm = self._lookup(instance.name) -- cgit From ded9447aca01d593b0efa91b5ec23ecf8aa97264 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 2 Aug 2011 15:43:36 -0400 Subject: Fixed merge issues --- nova/api/openstack/images.py | 3 --- nova/api/openstack/servers.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 43d976b8f..0834adfa5 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -197,9 +197,6 @@ class ControllerV11(Controller): def create(self, *args, **kwargs): raise webob.exc.HTTPMethodNotAllowed() - def create(self, *args, **kwargs): - raise webob.exc.HTTPMethodNotAllowed() - class ImageXMLSerializer(wsgi.XMLDictSerializer): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dfa150320..7b757143d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -20,7 +20,6 @@ import traceback from webob import exc from xml.dom import minidom import webob -from xml.dom import minidom from nova import compute from nova import exception @@ -178,6 +177,7 @@ class Controller(object): for key in self.actions.keys(): if key in body: return self.actions[key](body, req, id) + raise exc.HTTPNotImplemented() def _action_create_backup(self, input_dict, req, instance_id): -- cgit From 614895b6c93904888aab99d1507d94271d763c04 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 16:08:17 -0400 Subject: fix missing method call and add failing test --- nova/tests/test_xenapi.py | 9 +++++++++ nova/tests/xenapi/stubs.py | 4 ++++ nova/virt/xenapi_conn.py | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 6b7d5df76..9032c82ab 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -669,6 +669,14 @@ class XenAPIVMTestCase(test.TestCase): self.conn.spawn(instance, network_info) return instance + def test_revert_migration(self): + instance = self._create_instance() + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + stubs.stubout_loopingcall_start(self.stubs) + conn = xenapi_conn.get_connection(False) + conn.revert_migration(instance) + + class XenAPIDiffieHellmanTestCase(test.TestCase): """Unit tests for Diffie-Hellman code.""" @@ -842,6 +850,7 @@ class XenAPIMigrateInstance(test.TestCase): network_info, resize_instance=False) + class XenAPIImageTypeTestCase(test.TestCase): """Test ImageType class.""" diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 66c79d465..195e4d038 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -230,10 +230,14 @@ def stub_out_vm_methods(stubs): def fake_spawn_rescue(self, inst): inst._rescue = False + def fake_revert_migration(self, inst): + pass + stubs.Set(vmops.VMOps, "_shutdown", fake_shutdown) stubs.Set(vmops.VMOps, "_acquire_bootlock", fake_acquire_bootlock) stubs.Set(vmops.VMOps, "_release_bootlock", fake_release_bootlock) stubs.Set(vmops.VMOps, "spawn_rescue", fake_spawn_rescue) + stubs.Set(vmops.VMOps, "revert_migration", fake_revert_migration) class FakeSessionForVolumeTests(fake.SessionBase): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index a3d0abf9f..042bf2b02 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -190,7 +190,7 @@ class XenAPIConnection(driver.ComputeDriver): def revert_migration(self, instance): """Reverts a resize, powering back on the instance""" - self._vmops.revert_resize(instance) + self._vmops.revert_migration(instance) def finish_migration(self, instance, disk_info, network_info, resize_instance=False): -- cgit From 4c07cac5b0e79f3911fbcc392c3f9e7f07333968 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 2 Aug 2011 20:39:14 +0000 Subject: Fixed a missing space. --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index b83c4831c..5ea7a1c01 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -995,7 +995,7 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) - def set_host_powerstate(self, context, host, state): + def set_host_powerstate(self, context, host, state): """Reboots or shuts down the host.""" return self._call_compute_message("set_host_powerstate", context, instance_id=None, host=host, params={"state": state}) -- cgit From b5a4a8d6cc68fec2dab205c5da494bce69241c33 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 17:05:10 -0400 Subject: fix pep8 complaints --- nova/tests/test_xenapi.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 9032c82ab..8e3c88f8e 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -677,7 +677,6 @@ class XenAPIVMTestCase(test.TestCase): conn.revert_migration(instance) - class XenAPIDiffieHellmanTestCase(test.TestCase): """Unit tests for Diffie-Hellman code.""" def setUp(self): @@ -850,7 +849,6 @@ class XenAPIMigrateInstance(test.TestCase): network_info, resize_instance=False) - class XenAPIImageTypeTestCase(test.TestCase): """Test ImageType class.""" -- cgit From 9bac11d9ff9933460f7ddf1bd1dd77d4d3397e47 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 17:05:21 -0400 Subject: fix failing tests --- nova/db/sqlalchemy/api.py | 17 +++++++++++------ nova/tests/scheduler/test_scheduler.py | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ad935fa44..60220cd68 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3177,8 +3177,9 @@ def instance_metadata_delete_all(context, instance_id): @require_context @require_instance_exists -def instance_metadata_get_item(context, instance_id, key): - session = get_session() +def instance_metadata_get_item(context, instance_id, key, session=None): + if not session: + session = get_session() meta_result = session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ @@ -3202,7 +3203,8 @@ def instance_metadata_update_or_create(context, instance_id, metadata): meta_ref = None for key, value in metadata.iteritems(): try: - meta_ref = instance_metadata_get_item(context, instance_id, key) + meta_ref = instance_metadata_get_item( + context, instance_id, key, session) except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() meta_ref.update({"key": key, "value": value, @@ -3298,8 +3300,11 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key): - session = get_session() +def instance_type_extra_specs_get_item(context, instance_type_id, key, + session=None): + + if not session: + session = get_session() spec_result = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ @@ -3322,7 +3327,7 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id, for key, value in specs.iteritems(): try: spec_ref = instance_type_extra_specs_get_item( - context, instance_type_id, key) + context, instance_type_id, key, session) except exception.InstanceTypeExtraSpecsNotFound, e: spec_ref = models.InstanceTypeExtraSpecs() spec_ref.update({"key": key, "value": value, diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index a515636a3..0d7634d0d 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -51,10 +51,10 @@ FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' class TestDriver(driver.Scheduler): """Scheduler Driver for Tests""" - def schedule(self, context, topic, *args, **kwargs): + def schedule(context, topic, *args, **kwargs): return 'fallback_host' - def schedule_named_method(self, context, topic, num): + def schedule_named_method(context, topic, num): return 'named_host' -- 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 ++-- nova/tests/test_hosts.py | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'nova') 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): diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 1c7c21a1a..d8f90a109 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -49,10 +49,7 @@ def stub_set_host_enabled(context, host, enabled): def stub_set_host_powerstate(context, host, state): - # We'll simulate success and failure by assuming - # that 'host_c1' always succeeds, and 'host_c2' - # always fails - return state if host == "host_c1" else "running" + return state class FakeRequest(object): @@ -96,12 +93,14 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") - def test_power_state(self): + def test_host_power_state(self): en_body = {"power_state": "reboot"} result_c1 = self.controller.update(self.req, "host_c1", body=en_body) self.assertEqual(result_c1["power_state"], "reboot") + # Test invalid power_state + en_body = {"power_state": "invalid"} self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c2", body=en_body) + self.req, "host_c1", body=en_body) def test_bad_power_state_value(self): bad_body = {"power_state": "bad"} -- cgit From 8774c69c4a8d97ab2dac32c0f52e6963543a46f1 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 2 Aug 2011 17:11:53 -0400 Subject: raise correct error --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d99d7214c..8440421e1 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -264,7 +264,7 @@ class ZoneAwareScheduler(driver.Scheduler): """ if topic != "compute": - raise NotImplemented(_("Zone Aware Scheduler only understands " + raise NotImplementedError(_("Zone Aware Scheduler only understands " "Compute nodes (for now)")) num_instances = request_spec.get('num_instances', 1) -- 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') 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 a0ec6a6aa5ebdde1d099c5f6c03cf1dbd28441fa Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 3 Aug 2011 00:52:15 +0000 Subject: Removed duplicate methods created by previous merge. --- nova/compute/manager.py | 8 +------- nova/virt/driver.py | 4 ---- nova/virt/fake.py | 4 ---- nova/virt/hyperv.py | 4 ---- nova/virt/libvirt/connection.py | 4 ---- nova/virt/vmwareapi_conn.py | 4 ---- nova/virt/xenapi/vmops.py | 11 ----------- nova/virt/xenapi_conn.py | 4 ---- 8 files changed, 1 insertion(+), 42 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 37b920074..3c89042c8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -#: tabstop=4 shiftwidth=4 softtabstop=4 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -939,12 +939,6 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_power_state(self, context, instance_id=None, host=None, - power_state=None): - """Turns the specified host on/off, or reboots the host.""" - return self.driver.set_power_state(host, power_state) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/virt/driver.py b/nova/virt/driver.py index b32ed7c54..bbb17480d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -259,10 +259,6 @@ class ComputeDriver(object): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - raise NotImplementedError() - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 135b88ab8..888dca220 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -518,7 +518,3 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 553f85265..119def38b 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -505,7 +505,3 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 150478edf..46f3eef4b 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1586,7 +1586,3 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 6f68fd726..af547821f 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -198,10 +198,6 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" self._vmops.plug_vifs(instance, network_info) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 194c33d54..a90ae0128 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1042,17 +1042,6 @@ class VMOps(object): return xenapi_resp.details[-1] return resp["status"] - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - args = {"power_state": power_state} - xenapi_resp = self._call_xenhost("set_power_state", args) - try: - resp = json.loads(xenapi_resp) - except TypeError as e: - # Already logged; return the message - return xenapi_resp.details[-1] - return resp["power_state"] - def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 468e12696..5962b4ff8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -339,10 +339,6 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - return self._vmops.set_power_state(host, power_state) - class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- 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') 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 44964f41ed591c8ece72dbc36f5d668d2e279274 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 2 Aug 2011 22:22:34 -0500 Subject: renaming test_skip_unless_env_foo_exists() --- nova/tests/test_skip_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index dfbaac74b..b7979d34b 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -31,7 +31,7 @@ class ExampleSkipTestCase(test.TestCase): def test_skip_if_env_user_exists(self): x = 1 - @test.skip_unless(os.getenv("BLAH"), - "Skipping -- Environment variable BLAH does not exist") - def test_skip_unless_env_blah_exists(self): + @test.skip_unless(os.getenv("FOO"), + "Skipping -- Environment variable FOO does not exist") + def test_skip_unless_env_foo_exists(self): x = 1 -- cgit From 836c394def93a8589bcb7387862390b24b8fdab8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Tue, 2 Aug 2011 22:25:23 -0500 Subject: Resolved pep8 errors --- nova/api/openstack/create_instance_helper.py | 2 +- nova/test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') 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: diff --git a/nova/test.py b/nova/test.py index afc118100..f1b4050f9 100644 --- a/nova/test.py +++ b/nova/test.py @@ -67,6 +67,7 @@ class skip_test(object): _skipper.__doc__ = func.__doc__ return _skipper + class skip_if(object): """Decorator that skips a test if contition is true.""" def __init__(self, condition, msg): @@ -84,7 +85,7 @@ class skip_if(object): class skip_unless(object): - """Decorator that skips a test if condition is false.""" + """Decorator that skips a test if condition is not true.""" def __init__(self, condition, msg): self.condition = condition self.message = msg -- cgit From 2d2405e35d34c928c87d575bcfc7c74db9de6b1d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 11:31:10 +0000 Subject: Use flavorid only at the API level and use instance_type_id internally --- nova/compute/api.py | 2 +- nova/compute/manager.py | 21 ++++--- .../versions/036_change_flavor_id_in_migrations.py | 71 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 6 +- nova/tests/test_compute.py | 4 +- 5 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index aae16d1da..f42df015a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -955,7 +955,7 @@ class API(base.Base): {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, "instance_id": instance_ref['uuid'], - "flavor_id": new_instance_type['id']}}) + "instance_type_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") def add_fixed_ip(self, context, instance_id, network_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a2d84cd76..9b5fac58b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -736,8 +736,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - instance_type = self.db.instance_type_get_by_flavor_id(context, - migration_ref['old_flavor_id']) + instance_type = self.db.instance_type_get(context, + migration_ref['old_instance_type_id']) # Just roll back the record. There's no need to resize down since # the 'old' VM already has the preferred attributes @@ -758,7 +758,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def prep_resize(self, context, instance_id, flavor_id): + def prep_resize(self, context, instance_id, instance_type_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. @@ -777,16 +777,16 @@ class ComputeManager(manager.SchedulerDependentManager): old_instance_type = self.db.instance_type_get(context, instance_ref['instance_type_id']) - new_instance_type = self.db.instance_type_get_by_flavor_id(context, - flavor_id) + new_instance_type = self.db.instance_type_get(context, + instance_type_id) migration_ref = self.db.migration_create(context, {'instance_uuid': instance_ref['uuid'], 'source_compute': instance_ref['host'], 'dest_compute': FLAGS.host, 'dest_host': self.driver.get_host_ip_addr(), - 'old_flavor_id': old_instance_type['flavorid'], - 'new_flavor_id': flavor_id, + 'old_instance_type_id': old_instance_type['id'], + 'new_instance_type_id': instance_type_id, 'status': 'pre-migrating'}) LOG.audit(_('instance %s: migrating'), instance_ref['uuid'], @@ -849,9 +849,10 @@ class ComputeManager(manager.SchedulerDependentManager): resize_instance = False instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - if migration_ref['old_flavor_id'] != migration_ref['new_flavor_id']: - instance_type = self.db.instance_type_get_by_flavor_id(context, - migration_ref['new_flavor_id']) + if migration_ref['old_instance_type_id'] != \ + migration_ref['new_instance_type_id']: + instance_type = self.db.instance_type_get(context, + migration_ref['new_instance_type_id']) self.db.instance_update(context, instance_ref.uuid, dict(instance_type_id=instance_type['id'], memory_mb=instance_type['memory_mb'], diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py new file mode 100644 index 000000000..7ff709c17 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py @@ -0,0 +1,71 @@ +# 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.from sqlalchemy import * + +from sqlalchemy import Column, Integer, MetaData, Table + + +meta = MetaData() + + +# +# Tables to alter +# +# + +old_flavor_id = Column('old_flavor_id', Integer()) +new_flavor_id = Column('new_flavor_id', Integer()) +old_instance_type_id = Column('old_instance_type_id', Integer()) +new_instance_type_id = Column('new_instance_type_id', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instance_types = Table('instance_types', meta, autoload=True) + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(old_instance_type_id) + migrations.create_column(new_instance_type_id) + + # Convert flavor_id to instance_type_id + for instance_type in migrate_engine.execute(instance_types.select()): + migrate_engine.execute(migrations.update()\ + .where(migrations.c.old_flavor_id == instance_type.flavorid)\ + .values(old_instance_type_id=instance_type.id)) + migrate_engine.execute(migrations.update()\ + .where(migrations.c.new_flavor_id == instance_type.flavorid)\ + .values(new_instance_type_id=instance_type.id)) + + migrations.c.old_flavor_id.drop() + migrations.c.new_flavor_id.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(old_flavor_id) + migrations.create_column(new_flavor_id) + + # Convert instance_type_id to flavor_id + for instance_type in migrate_engine.execute(instance_types.select()): + migrate_engine.execute(migrations.update()\ + .where(migrations.c.old_instance_type_id == instance_type.id)\ + .values(old_flavor_id=instance_type.flavorid)) + migrate_engine.execute(migrations.update()\ + .where(migrations.c.new_instance_type_id == instance_type.id)\ + .values(new_flavor_id=instance_type.flavorid)) + + migrations.c.old_instance_type_id.drop() + migrations.c.new_instance_type_id.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 056259539..9f4c7a0aa 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -202,7 +202,7 @@ class Instance(BASE, NovaBase): hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - # aka flavor_id + # *not* flavor_id instance_type_id = Column(Integer) user_data = Column(Text) @@ -511,8 +511,8 @@ class Migration(BASE, NovaBase): source_compute = Column(String(255)) dest_compute = Column(String(255)) dest_host = Column(String(255)) - old_flavor_id = Column(Integer()) - new_flavor_id = Column(Integer()) + old_instance_type_id = Column(Integer()) + new_instance_type_id = Column(Integer()) instance_uuid = Column(String(255), ForeignKey('instances.uuid'), nullable=True) #TODO(_cerberus_): enum diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 879e4b9cb..bbf9ddcc6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -535,7 +535,9 @@ class ComputeTestCase(test.TestCase): db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, inst_ref['uuid'], 3) + new_instance_type_ref = db.instance_type_get_by_flavor_id(context, 3) + self.compute.prep_resize(context, inst_ref['uuid'], + new_instance_type_ref['id']) migration_ref = db.migration_get_by_instance_and_status(context, inst_ref['uuid'], 'pre-migrating') -- cgit From 8c77b6afa20c443916dd71572f22b52a5ecc88e9 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 3 Aug 2011 08:54:00 -0500 Subject: fumigate non-pep8 code --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/versions.py | 16 +++--- nova/api/openstack/views/versions.py | 6 +-- nova/tests/api/openstack/test_versions.py | 80 ++++++++++++++-------------- 4 files changed, 52 insertions(+), 52 deletions(-) (limited to 'nova') 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: diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 3ef72b7f6..e2f892fb6 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -34,23 +34,23 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" + "type": "application/vnd.openstack.compute-v1.0+json", } ], }, @@ -63,23 +63,23 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" + "type": "application/vnd.openstack.compute-v1.1+json", } ], }, diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 547289034..03da80818 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -42,10 +42,10 @@ class ViewBuilder(object): "links": [ { "rel": "self", - "href": self.generate_href(version['id'], req.path) - } + "href": self.generate_href(version['id'], req.path), + }, ], - "media-types": version['media-types'] + "media-types": version['media-types'], }) return dict(choices=version_objs) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index e68455778..7482368f9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -38,24 +38,24 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } + "type": "application/vnd.openstack.compute-v1.0+json", + }, ], }, "v1.1": { @@ -67,24 +67,24 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" - } + "type": "application/vnd.openstack.compute-v1.1+json", + }, ], }, } @@ -150,34 +150,34 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0/" + "href": "http://localhost/v1.0/", }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - } + "servers/api/v1.0/application.wadl", + }, ], "media-types": [ { "base": "application/xml", "type": "application/" - "vnd.openstack.compute-v1.0+xml" + "vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", "type": "application/" - "vnd.openstack.compute-v1.0+json" - } - ] - } + "vnd.openstack.compute-v1.0+json", + }, + ], + }, } self.assertEqual(expected, version) @@ -196,34 +196,34 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/" + "href": "http://localhost/v1.1/", }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - } + "servers/api/v1.1/application.wadl", + }, ], "media-types": [ { "base": "application/xml", - "type": "application/" - "vnd.openstack.compute-v1.1+xml" + "type": "application/", + "vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", "type": "application/" - "vnd.openstack.compute-v1.1+json" - } - ] - } + "vnd.openstack.compute-v1.1+json", + }, + ], + }, } self.assertEqual(expected, version) @@ -739,30 +739,30 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0/" + "href": "http://localhost/v1.0/", }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } + "type": "application/vnd.openstack.compute-v1.0+json", + }, ], }, } @@ -874,29 +874,29 @@ class VersionsSerializerTests(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/" + "href": "http://localhost/v1.1/", }, { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" + "type": "application/vnd.openstack.compute-v1.1+json", } ], }, -- cgit From 736a18ad49b535ce9d935bbe8c3d8e67f73a4d15 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 3 Aug 2011 08:57:27 -0500 Subject: whoops, got a little comma crazy --- nova/tests/api/openstack/test_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 7482368f9..1269f13c9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -214,7 +214,7 @@ class VersionsTest(test.TestCase): "media-types": [ { "base": "application/xml", - "type": "application/", + "type": "application/" "vnd.openstack.compute-v1.1+xml", }, { -- cgit From cb3bc75b316281866eaf32040dccc3fc3be257c2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 3 Aug 2011 10:03:15 -0400 Subject: PEP8 issue --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') 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 d543b9914506b39defbf94ebc63eb95f0d58a8cd Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 09:32:24 -0500 Subject: Removed dependancy on os.getenv. Test cases now raise Exception if they are not properly skipped. --- nova/tests/test_skip_examples.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index b7979d34b..e41cd848d 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -16,22 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. -import os - from nova import test class ExampleSkipTestCase(test.TestCase): - @test.skip_test("testing skipping") + @test.skip_test("Example usage of @test.skip_test()") def test_skip_test(self): - x = 1 + raise Exception("skip_test failed to work properly.") - @test.skip_if(os.getenv("USER"), - "Skiping -- Environment variable USER exists") + @test.skip_if(True, "Example usage of @test.skip_if()") def test_skip_if_env_user_exists(self): - x = 1 + raise Exception("skip_if failed to work properly.") - @test.skip_unless(os.getenv("FOO"), - "Skipping -- Environment variable FOO does not exist") + @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_env_foo_exists(self): - x = 1 + raise Exception("skip_unless failed to work properly.") -- cgit From c237b762381856b1041be06debf0deed4ae4e2a8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 10:44:09 -0500 Subject: replaced raise Exception with self.fail() --- nova/tests/test_skip_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index e41cd848d..41c3b1b5f 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -22,12 +22,12 @@ from nova import test class ExampleSkipTestCase(test.TestCase): @test.skip_test("Example usage of @test.skip_test()") def test_skip_test(self): - raise Exception("skip_test failed to work properly.") + self.fail("skip_test failed to work properly.") @test.skip_if(True, "Example usage of @test.skip_if()") def test_skip_if_env_user_exists(self): - raise Exception("skip_if failed to work properly.") + self.fail("skip_if failed to work properly.") @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_env_foo_exists(self): - raise Exception("skip_unless failed to work properly.") + self.fail("skip_unless failed to work properly.") -- cgit From 6166415d03db8f7af93778bdf7a735fb81e63fc5 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 3 Aug 2011 15:50:29 +0000 Subject: Default dns to '' if not present --- nova/virt/xenapi/vm_utils.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 60ef0df43..e08acbedc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1092,6 +1092,8 @@ def _prepare_injectables(inst, networks_info): ip_v6 = info['ip6s'][0] if len(info['dns']) > 0: dns = info['dns'][0] + else: + dns = '' interface_info = {'name': 'eth%d' % ifc_num, 'address': ip_v4 and ip_v4['ip'] or '', 'netmask': ip_v4 and ip_v4['netmask'] or '', -- cgit From 5027838724a5adfd0e15c4a10be723f03b6bbbae Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 3 Aug 2011 11:48:50 -0500 Subject: Removes extraneous body argument from server controller methods --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/servers.py | 12 ++++++------ nova/tests/api/openstack/test_servers.py | 24 ------------------------ 3 files changed, 7 insertions(+), 31 deletions(-) (limited to 'nova') 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: diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..d17714371 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -328,7 +328,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def reset_network(self, req, id, body): + def reset_network(self, req, id): """ Reset networking on an instance (admin only). @@ -343,7 +343,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def inject_network_info(self, req, id, body): + def inject_network_info(self, req, id): """ Inject network info for an instance (admin only). @@ -358,7 +358,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def pause(self, req, id, body): + def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: @@ -370,7 +370,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def unpause(self, req, id, body): + def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] try: @@ -382,7 +382,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def suspend(self, req, id, body): + def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] try: @@ -394,7 +394,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def resume(self, req, id, body): + def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] try: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4d42972c1..7062b0982 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1756,74 +1756,50 @@ class ServersTest(test.TestCase): def test_server_pause(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/pause') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_unpause(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/unpause') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_suspend(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/suspend') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_resume(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/resume') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_reset_network(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank('/v1.0/servers/1/reset_network') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) def test_server_inject_network_info(self): self.flags(allow_admin_api=True) - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) req = webob.Request.blank( '/v1.0/servers/1/inject_network_info') req.method = 'POST' req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) -- cgit From 4d500af8bdb4eda2d6040b54c217905cd7ed8bff Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 09:56:37 -0700 Subject: fix use of FLAGS in openstack API servers tests to use the new way --- nova/tests/api/openstack/test_servers.py | 35 +++++++++++++------------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4d42972c1..e927e6612 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -26,7 +26,6 @@ import webob from nova import context from nova import db from nova import exception -from nova import flags from nova import test from nova import utils import nova.api.openstack @@ -46,10 +45,6 @@ from nova.tests.api.openstack import common from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' @@ -269,8 +264,6 @@ class ServersTest(test.TestCase): self.sent_to_glance = {} fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) - self.allow_admin = FLAGS.allow_admin_api - self.webreq = common.webob_factory('/v1.0/servers') def test_get_server_by_id(self): @@ -767,7 +760,7 @@ class ServersTest(test.TestCase): self.assertEquals(ip.getAttribute('addr'), private) def test_get_server_by_id_with_addresses_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -811,7 +804,7 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) def test_get_server_by_id_with_addresses_v1_1_ipv6_disabled(self): - FLAGS.use_ipv6 = False + self.flags(use_ipv6=False) interfaces = [ { 'network': {'label': 'network_1'}, @@ -854,7 +847,7 @@ class ServersTest(test.TestCase): self.assertEqual(addresses, expected) def test_get_server_addresses_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -905,7 +898,7 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict, expected) def test_get_server_addresses_single_network_v1_1(self): - FLAGS.use_ipv6 = True + self.flags(use_ipv6=True) interfaces = [ { 'network': {'label': 'network_1'}, @@ -2329,7 +2322,7 @@ class ServersTest(test.TestCase): """ req = self.webreq('/1/migrate', 'POST') - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) self.resize_called = False def resize_mock(*args): @@ -2344,7 +2337,7 @@ class ServersTest(test.TestCase): def test_migrate_server_no_admin_api_fails(self): req = self.webreq('/1/migrate', 'POST') - FLAGS.allow_admin_api = False + self.flags(allow_admin_api=False) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -2425,7 +2418,7 @@ class ServersTest(test.TestCase): def test_create_backup(self): """The happy path for creating backups""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2445,7 +2438,7 @@ class ServersTest(test.TestCase): def test_create_backup_v1_1(self): """The happy path for creating backups through v1.1 api""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2465,7 +2458,7 @@ class ServersTest(test.TestCase): def test_create_backup_admin_api_off(self): """The happy path for creating backups""" - FLAGS.allow_admin_api = False + self.flags(allow_admin_api=False) body = { 'createBackup': { @@ -2483,7 +2476,7 @@ class ServersTest(test.TestCase): self.assertEqual(501, response.status_int) def test_create_backup_with_metadata(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2504,7 +2497,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_name(self): """Name is required for backups""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2522,7 +2515,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_rotation(self): """Rotation is required for backup requests""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2541,7 +2534,7 @@ class ServersTest(test.TestCase): def test_create_backup_no_backup_type(self): """Backup Type (daily or weekly) is required for backup requests""" - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = { 'createBackup': { @@ -2558,7 +2551,7 @@ class ServersTest(test.TestCase): self.assertEqual(400, response.status_int) def test_create_backup_bad_entity(self): - FLAGS.allow_admin_api = True + self.flags(allow_admin_api=True) body = {'createBackup': 'go'} req = webob.Request.blank('/v1.0/images') -- cgit From d7f704876f5cfab165c855170468274e57935d15 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 10:11:20 -0700 Subject: more cleanup of API tests regarding FLAGS --- nova/test.py | 6 ++++++ nova/tests/api/openstack/test_accounts.py | 6 +----- nova/tests/api/openstack/test_adminapi.py | 5 +---- nova/tests/api/openstack/test_extensions.py | 2 -- nova/tests/api/openstack/test_flavors_extra_specs.py | 3 --- nova/tests/api/openstack/test_images.py | 8 +------- nova/tests/api/openstack/test_servers.py | 1 + nova/tests/api/openstack/test_users.py | 7 +------ nova/tests/api/openstack/test_zones.py | 2 +- 9 files changed, 12 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 549aa6fcf..804dc8c37 100644 --- a/nova/test.py +++ b/nova/test.py @@ -100,6 +100,7 @@ class TestCase(unittest.TestCase): self.injected = [] self._services = [] self._original_flags = FLAGS.FlagValuesDict() + self._original_flags_verbosity = FLAGS.verbose def tearDown(self): """Runs after each test method to tear down test environment.""" @@ -157,6 +158,11 @@ class TestCase(unittest.TestCase): FLAGS.Reset() for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) + FLAGS.verbose = self._original_flags_verbosity + + def set_flags_verbosity(self, verbose=True): + """Set the FLAGS verbosity for a test""" + FLAGS.verbose = verbose def start_service(self, name, host=None, **kwargs): host = host and host or uuid.uuid4().hex diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py index 89dbf5213..b1ca546d2 100644 --- a/nova/tests/api/openstack/test_accounts.py +++ b/nova/tests/api/openstack/test_accounts.py @@ -18,17 +18,12 @@ import json import webob -from nova import flags from nova import test from nova.api.openstack import accounts from nova.auth.manager import User from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def fake_init(self): self.manager = fakes.FakeAuthManager() @@ -41,6 +36,7 @@ class AccountsTest(test.TestCase): def setUp(self): super(AccountsTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) self.stubs.Set(accounts.Controller, '__init__', fake_init) self.stubs.Set(accounts.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index b83de40cf..9d03a7f9d 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -18,12 +18,9 @@ import webob -from nova import flags from nova import test from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS - class AdminAPITest(test.TestCase): @@ -31,7 +28,7 @@ class AdminAPITest(test.TestCase): super(AdminAPITest, self).setUp() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - self.allow_admin = FLAGS.allow_admin_api + self.set_flags_verbosity(True) def test_admin_enabled(self): self.flags(allow_admin_api=True) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 47c37225c..409fa0e71 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -21,7 +21,6 @@ import webob from xml.etree import ElementTree from nova import context -from nova import flags from nova import test from nova.api import openstack from nova.api.openstack import extensions @@ -29,7 +28,6 @@ from nova.api.openstack import flavors from nova.api.openstack import wsgi from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS NS = "{http://docs.openstack.org/compute/api/v1.1}" ATOMNS = "{http://www.w3.org/2005/Atom}" response_body = "Try to say this Mr. Knox, sir..." diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index d386958db..ccd1b0d9f 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -21,15 +21,12 @@ import webob import os.path -from nova import flags from nova import test from nova.api import openstack from nova.api.openstack import extensions from nova.tests.api.openstack import fakes import nova.wsgi -FLAGS = flags.FLAGS - def return_create_flavor_extra_specs(context, flavor_id, extra_specs): return stub_flavor_extra_specs() diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 942c0b333..38cfc287c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -34,7 +34,6 @@ import webob from glance import client as glance_client from nova import context from nova import exception -from nova import flags from nova import test from nova import utils import nova.api.openstack @@ -42,9 +41,6 @@ from nova.api.openstack import images from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS - - class _BaseImageServiceTests(test.TestCase): """Tasks to test for all image services""" @@ -328,8 +324,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def setUp(self): """Run before each test.""" super(ImageControllerWithGlanceServiceTest, self).setUp() - self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.glance.GlanceImageService' + self.flags(image_service='nova.image.glance.GlanceImageService') self.stubs = stubout.StubOutForTesting() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) @@ -342,7 +337,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def tearDown(self): """Run after each test.""" self.stubs.UnsetAll() - FLAGS.image_service = self.orig_image_service super(ImageControllerWithGlanceServiceTest, self).tearDown() def _applicable_fixture(self, fixture, user_id): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e927e6612..796c494d4 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -230,6 +230,7 @@ class ServersTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServersTest, self).setUp() + self.set_flags_verbosity(True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py index 705c02f6b..649538f11 100644 --- a/nova/tests/api/openstack/test_users.py +++ b/nova/tests/api/openstack/test_users.py @@ -17,7 +17,6 @@ import json import webob -from nova import flags from nova import test from nova import utils from nova.api.openstack import users @@ -25,10 +24,6 @@ from nova.auth.manager import User, Project from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def fake_init(self): self.manager = fakes.FakeAuthManager() @@ -41,6 +36,7 @@ class UsersTest(test.TestCase): def setUp(self): super(UsersTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) self.stubs.Set(users.Controller, '__init__', fake_init) self.stubs.Set(users.Controller, '_check_admin', @@ -56,7 +52,6 @@ class UsersTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - self.allow_admin = FLAGS.allow_admin_api fakemgr = fakes.FakeAuthManager() fakemgr.add_user(User('id1', 'guy1', 'acc1', 'secret1', False)) fakemgr.add_user(User('id2', 'guy2', 'acc2', 'secret2', True)) diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 3deb844aa..442ee4313 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -29,7 +29,6 @@ from nova.scheduler import api FLAGS = flags.FLAGS -FLAGS.verbose = True def zone_get(context, zone_id): @@ -96,6 +95,7 @@ class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() self.flags(allow_admin_api=True) + self.set_flags_verbosity(True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) -- cgit From 916e0ce0997bdf3135684865eff6fadcda95752b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:13:53 -0400 Subject: remove unused imports --- nova/api/openstack/create_instance_helper.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 53e814cd5..e4b897cd6 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -14,8 +14,6 @@ # under the License. import base64 -import re -import webob from webob import exc from xml.dom import minidom -- cgit From 91e16e057f083d1a0b8dffaa00b5979c12c23edc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:30:45 -0400 Subject: fix potential runtime exception The exception could occur if a client were to create an APIRouter object. The fix relies on more established OOP patterns. --- nova/api/openstack/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 868b98a31..8d7f7a405 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -81,7 +81,10 @@ class APIRouter(base_wsgi.Router): self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) - def _setup_routes(self, mapper, version): + def _setup_routes(self, mapper): + raise NotImplementedError("you must implement _setup_routes") + + def _setup_base_routes(self, mapper, version): """Routes common to all versions.""" server_members = self.server_members @@ -147,7 +150,7 @@ class APIRouterV10(APIRouter): """Define routes specific to OpenStack API V1.0.""" def _setup_routes(self, mapper): - super(APIRouterV10, self)._setup_routes(mapper, '1.0') + self._setup_base_routes(mapper, '1.0') mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, @@ -163,7 +166,7 @@ class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" def _setup_routes(self, mapper): - super(APIRouterV11, self)._setup_routes(mapper, '1.1') + self._setup_base_routes(mapper, '1.1') image_metadata_controller = image_metadata.create_resource() mapper.resource("image_meta", "metadata", controller=image_metadata_controller, -- cgit From a049e682b54525d2814f20b08ca64320040675b8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:46:02 -0400 Subject: fix undefined variable error --- nova/scheduler/least_cost.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 8c400d476..329107efe 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -96,7 +96,8 @@ class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): cost_fn_str=cost_fn_str) try: - weight = getattr(FLAGS, "%s_weight" % cost_fn.__name__) + flag_name = "%s_weight" % cost_fn.__name__ + weight = getattr(FLAGS, flag_name) except AttributeError: raise exception.SchedulerWeightFlagNotFound( flag_name=flag_name) -- cgit From 933587d821797037bb765e9d0bd5ea063c07785b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:47:35 -0400 Subject: fix duplicate function name --- nova/tests/test_instance_types_extra_specs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index 393ed1e36..205601277 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -136,7 +136,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): "m1.small") self.assertEquals(instance_type['extra_specs'], {}) - def test_instance_type_get_with_extra_specs(self): + def test_instance_type_get_by_flavor_id_with_extra_specs(self): instance_type = db.api.instance_type_get_by_flavor_id( context.get_admin_context(), 105) -- cgit From 98307324d7b1cce2f7312d1195c9674f0e0323b6 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:49:14 -0400 Subject: use correct exception name --- nova/virt/vmwareapi/vif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py index b3e43b209..fb6548b34 100644 --- a/nova/virt/vmwareapi/vif.py +++ b/nova/virt/vmwareapi/vif.py @@ -63,7 +63,7 @@ class VMWareVlanBridgeDriver(VIFDriver): vswitch_associated = network_utils.get_vswitch_for_vlan_interface( session, vlan_interface) if vswitch_associated is None: - raise exception.SwicthNotFoundForNetworkAdapter( + raise exception.SwitchNotFoundForNetworkAdapter( adapter=vlan_interface) # Check whether bridge already exists and retrieve the the ref of the # network whose name_label is "bridge" -- cgit From 4aada43cc0d1a3cb32094d6d6be1eb662f01d063 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:51:47 -0400 Subject: use an existing exception --- nova/virt/xenapi/vm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 60ef0df43..8a715af35 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -794,7 +794,7 @@ def get_vdi_for_vm_safely(session, vm_ref): else: num_vdis = len(vdi_refs) if num_vdis != 1: - raise exception.Exception(_("Unexpected number of VDIs" + raise exception.Error(_("Unexpected number of VDIs" "(%(num_vdis)s) found" " for VM %(vm_ref)s") % locals()) -- cgit From 6203ae5d5b285d62bd2c1bf1f2f11d3b64b53511 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 15:05:57 -0400 Subject: pep8 fixes --- nova/scheduler/zone_aware_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 8440421e1..297922570 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -264,8 +264,8 @@ class ZoneAwareScheduler(driver.Scheduler): """ if topic != "compute": - raise NotImplementedError(_("Zone Aware Scheduler only understands " - "Compute nodes (for now)")) + raise NotImplementedError(_("Zone Aware Scheduler only understands" + " Compute nodes (for now)")) num_instances = request_spec.get('num_instances', 1) instance_type = request_spec['instance_type'] -- cgit From a5add8b30c96ad1d83eebcd9756b0f358c9cb725 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 15:16:38 -0400 Subject: follow convention when raising exceptions --- nova/api/openstack/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8d7f7a405..0af39c2df 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -82,7 +82,7 @@ class APIRouter(base_wsgi.Router): super(APIRouter, self).__init__(mapper) def _setup_routes(self, mapper): - raise NotImplementedError("you must implement _setup_routes") + raise NotImplementedError(_("You must implement _setup_routes.")) def _setup_base_routes(self, mapper, version): """Routes common to all versions.""" -- cgit From 8efe41aaa4993d0aa9ad381d202ba7b7d025939e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 12:22:58 -0700 Subject: switch FLAGS.* = in tests to self.flags(...) remove unused cases of FLAGS from tests modified test.TestCase's flags() to allow multiple overrides added missing license to test_rpc_amqp.py --- nova/test.py | 8 +++----- nova/tests/hyperv_unittest.py | 5 +---- nova/tests/test_auth.py | 4 ++-- nova/tests/test_host_filter.py | 12 +++--------- nova/tests/test_ipv6.py | 3 --- nova/tests/test_libvirt.py | 23 +++++++++-------------- nova/tests/test_network.py | 2 -- nova/tests/test_quota.py | 42 +++++++++++++++++------------------------- nova/tests/test_rpc.py | 2 -- nova/tests/test_rpc_amqp.py | 24 ++++++++++++++++++++++-- nova/tests/test_service.py | 1 - nova/tests/test_xenapi.py | 12 ++++++------ 12 files changed, 63 insertions(+), 75 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 804dc8c37..fa5a662fd 100644 --- a/nova/test.py +++ b/nova/test.py @@ -142,11 +142,9 @@ class TestCase(unittest.TestCase): def flags(self, **kw): """Override flag variables for a test.""" for k, v in kw.iteritems(): - if k in self.flag_overrides: - self.reset_flags() - raise Exception( - 'trying to override already overriden flag: %s' % k) - self.flag_overrides[k] = getattr(FLAGS, k) + # Store original flag value if it's not been overriden yet + if k not in self.flag_overrides: + self.flag_overrides[k] = getattr(FLAGS, k) setattr(FLAGS, k, v) def reset_flags(self): diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 0ea196950..d346d0a70 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -21,13 +21,9 @@ import random from nova import context from nova import db -from nova import flags from nova import test from nova.virt import hyperv -FLAGS = flags.FLAGS -FLAGS.connection_type = 'hyperv' - class HyperVTestCase(test.TestCase): """Test cases for the Hyper-V driver""" @@ -36,6 +32,7 @@ class HyperVTestCase(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) + self.flags(connection_type='hyperv') def test_create_destroy(self): """Create a VM and destroy it""" diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 7c0f783bb..2e24b7d6e 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -83,9 +83,9 @@ class user_and_project_generator(object): class _AuthManagerBaseTestCase(test.TestCase): def setUp(self): - FLAGS.auth_driver = self.auth_driver super(_AuthManagerBaseTestCase, self).setUp() - self.flags(connection_type='fake') + self.flags(auth_driver=self.auth_driver, + connection_type='fake') self.manager = manager.AuthManager(new=True) self.manager.mc.cache = {} diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 438f3e522..3a1389a49 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -19,12 +19,9 @@ Tests For Scheduler Host Filters. import json from nova import exception -from nova import flags from nova import test from nova.scheduler import host_filter -FLAGS = flags.FLAGS - class FakeZoneManager: pass @@ -57,9 +54,9 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): - self.old_flag = FLAGS.default_host_filter - FLAGS.default_host_filter = \ - 'nova.scheduler.host_filter.AllHostsFilter' + super(HostFilterTestCase, self).setUp() + default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' + self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', memory_mb=50, vcpus=10, @@ -76,9 +73,6 @@ class HostFilterTestCase(test.TestCase): states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)} self.zone_manager.service_states = states - def tearDown(self): - FLAGS.default_host_filter = self.old_flag - def test_choose_filter(self): # Test default filter ... hf = host_filter.choose_host_filter() diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 11dc2ec98..d123df6f1 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -16,15 +16,12 @@ """Test suite for IPv6.""" -from nova import flags from nova import ipv6 from nova import log as logging from nova import test LOG = logging.getLogger('nova.tests.test_ipv6') -FLAGS = flags.FLAGS - import sys diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index cf25ce215..f8b866985 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -38,7 +38,6 @@ from nova.virt.libvirt import firewall libvirt = None FLAGS = flags.FLAGS -flags.DECLARE('instances_path', 'nova.compute.manager') def _concurrency(wait, done, target): @@ -93,6 +92,7 @@ def _setup_networking(instance_id, ip='1.2.3.4'): class CacheConcurrencyTestCase(test.TestCase): def setUp(self): super(CacheConcurrencyTestCase, self).setUp() + self.flags(instances_path='nova.compute.manager') def fake_exists(fname): basedir = os.path.join(FLAGS.instances_path, '_base') @@ -158,7 +158,7 @@ class LibvirtConnTestCase(test.TestCase): self.context = context.RequestContext(self.user_id, self.project_id) self.network = utils.import_object(FLAGS.network_manager) self.context = context.get_admin_context() - FLAGS.instances_path = '' + self.flags(instances_path='') self.call_libvirt_dependant_setup = False self.test_ip = '10.11.12.13' @@ -322,7 +322,7 @@ class LibvirtConnTestCase(test.TestCase): if not self.lazy_load_library_exists(): return - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') # Start test image_service = utils.import_object(FLAGS.image_service) @@ -357,7 +357,7 @@ class LibvirtConnTestCase(test.TestCase): if not self.lazy_load_library_exists(): return - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') # Start test image_service = utils.import_object(FLAGS.image_service) @@ -521,7 +521,7 @@ class LibvirtConnTestCase(test.TestCase): 'disk.local')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): - FLAGS.libvirt_type = libvirt_type + self.flags(libvirt_type=libvirt_type) conn = connection.LibvirtConnection(True) uri = conn.get_uri() @@ -546,9 +546,9 @@ class LibvirtConnTestCase(test.TestCase): # checking against that later on. This way we make sure the # implementation doesn't fiddle around with the FLAGS. testuri = 'something completely different' - FLAGS.libvirt_uri = testuri + self.flags(libvirt_uri=testuri) for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): - FLAGS.libvirt_type = libvirt_type + self.flags(libvirt_type=libvirt_type) conn = connection.LibvirtConnection(True) uri = conn.get_uri() self.assertEquals(uri, testuri) @@ -556,8 +556,7 @@ class LibvirtConnTestCase(test.TestCase): def test_update_available_resource_works_correctly(self): """Confirm compute_node table is updated successfully.""" - org_path = FLAGS.instances_path = '' - FLAGS.instances_path = '.' + self.flags(instances_path='.') # Prepare mocks def getVersion(): @@ -604,12 +603,10 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(compute_node['hypervisor_version'] > 0) db.service_destroy(self.context, service_ref['id']) - FLAGS.instances_path = org_path def test_update_resource_info_no_compute_record_found(self): """Raise exception if no recorde found on services table.""" - org_path = FLAGS.instances_path = '' - FLAGS.instances_path = '.' + self.flags(instances_path='.') self.create_fake_libvirt_mock() self.mox.ReplayAll() @@ -618,8 +615,6 @@ class LibvirtConnTestCase(test.TestCase): conn.update_available_resource, self.context, 'dummy') - FLAGS.instances_path = org_path - def test_ensure_filtering_rules_for_instance_timeout(self): """ensure_filtering_fules_for_instance() finishes with timeout.""" # Skip if non-libvirt environment diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 28f50d328..2ca8b64f4 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -17,7 +17,6 @@ from nova import db from nova import exception -from nova import flags from nova import log as logging from nova import test from nova.network import manager as network_manager @@ -26,7 +25,6 @@ from nova.network import manager as network_manager import mox -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.network') diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 92393b536..f4b481ebe 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -114,9 +114,7 @@ class QuotaTestCase(test.TestCase): db.quota_destroy_all_by_project(self.context, self.project_id) def test_unlimited_instances(self): - FLAGS.quota_instances = 2 - FLAGS.quota_ram = -1 - FLAGS.quota_cores = -1 + self.flags(quota_instances=2, quota_ram=-1, quota_cores=-1) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -130,9 +128,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_ram(self): - FLAGS.quota_instances = -1 - FLAGS.quota_ram = 2 * 2048 - FLAGS.quota_cores = -1 + self.flags(quota_instances=-1, quota_ram=2 * 2048, quota_cores=-1) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -146,9 +142,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_cores(self): - FLAGS.quota_instances = -1 - FLAGS.quota_ram = -1 - FLAGS.quota_cores = 2 + self.flags(quota_instances=-1, quota_ram=-1, quota_cores=2) instance_type = self._get_instance_type('m1.small') num_instances = quota.allowed_instances(self.context, 100, instance_type) @@ -162,8 +156,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(num_instances, 101) def test_unlimited_volumes(self): - FLAGS.quota_volumes = 10 - FLAGS.quota_gigabytes = -1 + self.flags(quota_volumes=10, quota_gigabytes=-1) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) db.quota_create(self.context, self.project_id, 'volumes', None) @@ -173,8 +166,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(volumes, 101) def test_unlimited_gigabytes(self): - FLAGS.quota_volumes = -1 - FLAGS.quota_gigabytes = 10 + self.flags(quota_volumes=-1, quota_gigabytes=10) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) db.quota_create(self.context, self.project_id, 'gigabytes', None) @@ -184,7 +176,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(volumes, 101) def test_unlimited_floating_ips(self): - FLAGS.quota_floating_ips = 10 + self.flags(quota_floating_ips=10) floating_ips = quota.allowed_floating_ips(self.context, 100) self.assertEqual(floating_ips, 10) db.quota_create(self.context, self.project_id, 'floating_ips', None) @@ -194,7 +186,7 @@ class QuotaTestCase(test.TestCase): self.assertEqual(floating_ips, 101) def test_unlimited_metadata_items(self): - FLAGS.quota_metadata_items = 10 + self.flags(quota_metadata_items=10) items = quota.allowed_metadata_items(self.context, 100) self.assertEqual(items, 10) db.quota_create(self.context, self.project_id, 'metadata_items', None) @@ -286,49 +278,49 @@ class QuotaTestCase(test.TestCase): metadata=metadata) def test_default_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 55 + self.flags(quota_max_injected_files=55) self.assertEqual(quota.allowed_injected_files(self.context, 100), 55) def test_overridden_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 5 + self.flags(quota_max_injected_files=5) db.quota_create(self.context, self.project_id, 'injected_files', 77) self.assertEqual(quota.allowed_injected_files(self.context, 100), 77) def test_unlimited_default_allowed_injected_files(self): - FLAGS.quota_max_injected_files = -1 + self.flags(quota_max_injected_files=-1) self.assertEqual(quota.allowed_injected_files(self.context, 100), 100) def test_unlimited_db_allowed_injected_files(self): - FLAGS.quota_max_injected_files = 5 + self.flags(quota_max_injected_files=5) db.quota_create(self.context, self.project_id, 'injected_files', None) self.assertEqual(quota.allowed_injected_files(self.context, 100), 100) def test_default_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 12345) def test_overridden_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', 5678) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 5678) def test_unlimited_default_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = -1 + self.flags(quota_max_injected_file_content_bytes=-1) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 23456) def test_unlimited_db_allowed_injected_file_content_bytes(self): - FLAGS.quota_max_injected_file_content_bytes = 12345 + self.flags(quota_max_injected_file_content_bytes=12345) db.quota_create(self.context, self.project_id, 'injected_file_content_bytes', None) limit = quota.allowed_injected_file_content_bytes(self.context, 23456) self.assertEqual(limit, 23456) def _create_with_injected_files(self, files): - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') api = compute.API(image_service=self.StubImageService()) inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, min_count=1, max_count=1, @@ -336,7 +328,7 @@ class QuotaTestCase(test.TestCase): injected_files=files) def test_no_injected_files(self): - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') api = compute.API(image_service=self.StubImageService()) inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, instance_type=inst_type, image_href='3') diff --git a/nova/tests/test_rpc.py b/nova/tests/test_rpc.py index 2d2436175..ba9c0a859 100644 --- a/nova/tests/test_rpc.py +++ b/nova/tests/test_rpc.py @@ -20,13 +20,11 @@ Unit Tests for remote procedure calls using queue """ from nova import context -from nova import flags from nova import log as logging from nova import rpc from nova import test -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.rpc') diff --git a/nova/tests/test_rpc_amqp.py b/nova/tests/test_rpc_amqp.py index d29f7ae32..2215a908b 100644 --- a/nova/tests/test_rpc_amqp.py +++ b/nova/tests/test_rpc_amqp.py @@ -1,12 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# Administrator of the National Aeronautics and Space Administration. +# 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. + +""" +Tests For RPC AMQP. +""" + from nova import context -from nova import flags from nova import log as logging from nova import rpc from nova.rpc import amqp from nova import test -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.rpc') diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index bbf47b50f..8f92406ff 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -33,7 +33,6 @@ from nova import manager from nova import wsgi from nova.compute import manager as compute_manager -FLAGS = flags.FLAGS flags.DEFINE_string("fake_manager", "nova.tests.test_service.FakeManager", "Manager for testing") diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..1ba2bf356 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -71,9 +71,9 @@ class XenAPIVolumeTestCase(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) - FLAGS.target_host = '127.0.0.1' - FLAGS.xenapi_connection_url = 'test_url' - FLAGS.xenapi_connection_password = 'test_pass' + self.flags(target_host='127.0.0.1', + xenapi_connection_url='test_url', + xenapi_connection_password='test_pass') db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() @@ -719,9 +719,9 @@ class XenAPIMigrateInstance(test.TestCase): def setUp(self): super(XenAPIMigrateInstance, self).setUp() self.stubs = stubout.StubOutForTesting() - FLAGS.target_host = '127.0.0.1' - FLAGS.xenapi_connection_url = 'test_url' - FLAGS.xenapi_connection_password = 'test_pass' + self.flags(target_host='127.0.0.1', + xenapi_connection_url='test_url', + xenapi_connection_password='test_pass') db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() -- cgit From 25655e0bcea99ea27108d954104138a09f922f30 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 12:26:51 -0700 Subject: fix test_s3 FLAGS uses --- nova/tests/api/openstack/__init__.py | 3 --- nova/tests/image/test_s3.py | 10 +--------- 2 files changed, 1 insertion(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index bfb424afe..458434a81 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -22,14 +22,11 @@ import webob.dec from nova import test from nova import context -from nova import flags from nova.api.openstack.limits import RateLimitingMiddleware from nova.api.openstack.common import limited from nova.tests.api.openstack import fakes from webob import Request -FLAGS = flags.FLAGS - @webob.dec.wsgify def simple_wsgi(req): diff --git a/nova/tests/image/test_s3.py b/nova/tests/image/test_s3.py index 231e109f8..f1ceeb7fe 100644 --- a/nova/tests/image/test_s3.py +++ b/nova/tests/image/test_s3.py @@ -16,12 +16,9 @@ # under the License. from nova import context -from nova import flags from nova import test from nova.image import s3 -FLAGS = flags.FLAGS - ami_manifest_xml = """ @@ -59,15 +56,10 @@ ami_manifest_xml = """ class TestS3ImageService(test.TestCase): def setUp(self): super(TestS3ImageService, self).setUp() - self.orig_image_service = FLAGS.image_service - FLAGS.image_service = 'nova.image.fake.FakeImageService' + self.flags(image_service='nova.image.fake.FakeImageService') self.image_service = s3.S3ImageService() self.context = context.RequestContext(None, None) - def tearDown(self): - super(TestS3ImageService, self).tearDown() - FLAGS.image_service = self.orig_image_service - def _assertEqualList(self, list0, list1, keys): self.assertEqual(len(list0), len(list1)) key = keys[0] -- cgit From e7cb0c70384fc247a7f330ed7c4db5a3b0de814c Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 16:45:46 -0400 Subject: align multi-line string --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 297922570..9ac0f94ed 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -265,7 +265,7 @@ class ZoneAwareScheduler(driver.Scheduler): if topic != "compute": raise NotImplementedError(_("Zone Aware Scheduler only understands" - " Compute nodes (for now)")) + " Compute nodes (for now)")) num_instances = request_spec.get('num_instances', 1) instance_type = request_spec['instance_type'] -- cgit From 8b0ebd90edb47be344b355334ae16d6b7715087d Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 17:02:45 -0400 Subject: all subclasses of ComputeDriver should fully implement the interface of the destroy method. --- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/vmwareapi_conn.py | 2 +- nova/virt/xenapi_conn.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 2898f23a4..b7396d61c 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -293,7 +293,7 @@ class FakeConnection(driver.ComputeDriver): """ pass - def destroy(self, instance, network_info): + def destroy(self, instance, network_info, cleanup=True): key = instance.name if key in self.instances: del self.instances[key] diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 315f635c7..1e4cce10c 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -373,7 +373,7 @@ class HyperVConnection(driver.ComputeDriver): raise exception.InstanceNotFound(instance_id=instance.id) self._set_vm_state(instance.name, 'Reboot') - def destroy(self, instance): + def destroy(self, instance, network_info, cleanup=True): """Destroy the VM. Also destroy the associated VHD disk files""" LOG.debug(_("Got request to destroy vm %s"), instance.name) vm = self._lookup(instance.name) diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index ce57847b2..10d80c0a0 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -136,7 +136,7 @@ class VMWareESXConnection(driver.ComputeDriver): """Reboot VM instance.""" self._vmops.reboot(instance, network_info) - def destroy(self, instance, network_info): + def destroy(self, instance, network_info, cleanup=True): """Destroy VM instance.""" self._vmops.destroy(instance, network_info) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 042bf2b02..571638496 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -216,7 +216,7 @@ class XenAPIConnection(driver.ComputeDriver): """ self._vmops.inject_file(instance, b64_path, b64_contents) - def destroy(self, instance, network_info): + def destroy(self, instance, network_info, cleanup=True): """Destroy VM instance""" self._vmops.destroy(instance, network_info) -- cgit From ff5dbc6d985d11ef937baa930ee96c93cbd8ccf9 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:06:56 -0700 Subject: fix more tests that use FLAGS setting --- nova/tests/integrated/integrated_helpers.py | 4 +--- nova/tests/integrated/test_extensions.py | 5 ----- nova/tests/integrated/test_login.py | 4 ---- nova/tests/integrated/test_servers.py | 5 ----- nova/tests/integrated/test_volumes.py | 5 ----- nova/tests/integrated/test_xml.py | 5 ----- 6 files changed, 1 insertion(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 47bd8c1e4..049ddbc60 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -23,7 +23,6 @@ import random import string from nova import exception -from nova import flags from nova import service from nova import test # For the flags from nova.auth import manager @@ -32,8 +31,6 @@ from nova.log import logging from nova.tests.integrated.api import client -FLAGS = flags.FLAGS - LOG = logging.getLogger('nova.tests.integrated') @@ -151,6 +148,7 @@ class _IntegratedTestBase(test.TestCase): f = self._get_flags() self.flags(**f) + self.set_flags_verbosity(True) def fake_get_image_service(image_href): image_id = int(str(image_href).split('/')[-1]) diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index 0d4ee8cab..c22cf0be0 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -17,7 +17,6 @@ import os -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers @@ -25,10 +24,6 @@ from nova.tests.integrated import integrated_helpers LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class ExtensionsTest(integrated_helpers._IntegratedTestBase): def _get_flags(self): f = super(ExtensionsTest, self)._get_flags() diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index a5180b6bc..06359a52f 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -17,7 +17,6 @@ import unittest -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -25,9 +24,6 @@ from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - class LoginTest(integrated_helpers._IntegratedTestBase): def test_login(self): diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 67b3c485a..150279a95 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -18,7 +18,6 @@ import time import unittest -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -27,10 +26,6 @@ from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class ServersTest(integrated_helpers._IntegratedTestBase): def test_get_servers(self): """Simple check that listing servers works.""" diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index e9fb3c4d1..d3e936462 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -18,7 +18,6 @@ import unittest import time -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -28,10 +27,6 @@ from nova.volume import driver LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class VolumesTest(integrated_helpers._IntegratedTestBase): def setUp(self): super(VolumesTest, self).setUp() diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py index fde32f797..74baaacc2 100644 --- a/nova/tests/integrated/test_xml.py +++ b/nova/tests/integrated/test_xml.py @@ -15,7 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from nova import flags from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.api.openstack import common @@ -24,10 +23,6 @@ from nova.api.openstack import common LOG = logging.getLogger('nova.tests.integrated') -FLAGS = flags.FLAGS -FLAGS.verbose = True - - class XmlTests(integrated_helpers._IntegratedTestBase): """"Some basic XML sanity checks.""" -- cgit From 4f9f56d8e9917f5db0c5f76653d7ee3a2db52a5f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:08:22 -0700 Subject: fix scheduler tests that set FLAGS --- nova/tests/scheduler/test_scheduler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 6a56a57db..f60eb6433 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -962,13 +962,10 @@ class ZoneRedirectTest(test.TestCase): self.stubs.Set(db, 'zone_get_all', zone_get_all) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) - - self.enable_zone_routing = FLAGS.enable_zone_routing - FLAGS.enable_zone_routing = True + self.flags(enable_zone_routing=True) def tearDown(self): self.stubs.UnsetAll() - FLAGS.enable_zone_routing = self.enable_zone_routing super(ZoneRedirectTest, self).tearDown() def test_trap_found_locally(self): @@ -998,7 +995,7 @@ class ZoneRedirectTest(test.TestCase): self.assertEquals(e.results['magic'], 'found me') def test_routing_flags(self): - FLAGS.enable_zone_routing = False + self.flags(enable_zone_routing=False) decorator = FakeRerouteCompute("foo") self.assertRaises(exception.InstanceNotFound, decorator(go_boom), None, None, 1) -- cgit From 3765fca2cfd323c047141a27df248d9617d71ad0 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:11:37 -0700 Subject: dist scheduler flag setting fixes --- nova/tests/scheduler/test_host_filter.py | 11 ++--------- nova/tests/scheduler/test_least_cost_scheduler.py | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index b1892dab4..b016923c4 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -19,12 +19,9 @@ Tests For Scheduler Host Filters. import json from nova import exception -from nova import flags from nova import test from nova.scheduler import host_filter -FLAGS = flags.FLAGS - class FakeZoneManager: pass @@ -57,9 +54,8 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): - self.old_flag = FLAGS.default_host_filter - FLAGS.default_host_filter = \ - 'nova.scheduler.host_filter.AllHostsFilter' + default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' + self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', memory_mb=50, vcpus=10, @@ -98,9 +94,6 @@ class HostFilterTestCase(test.TestCase): host09['xpu_arch'] = 'fermi' host09['xpu_info'] = 'Tesla 2150' - def tearDown(self): - FLAGS.default_host_filter = self.old_flag - def test_choose_filter(self): # Test default filter ... hf = host_filter.choose_host_filter() diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py index 49791053e..fbe6b2f77 100644 --- a/nova/tests/scheduler/test_least_cost_scheduler.py +++ b/nova/tests/scheduler/test_least_cost_scheduler.py @@ -16,13 +16,11 @@ Tests For Least Cost Scheduler """ -from nova import flags from nova import test from nova.scheduler import least_cost from nova.tests.scheduler import test_zone_aware_scheduler MB = 1024 * 1024 -FLAGS = flags.FLAGS class FakeHost(object): @@ -95,10 +93,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_noop_cost_fn(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn', - ] - FLAGS.noop_cost_fn_weight = 1 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.noop_cost_fn'], + noop_cost_fn_weight=1) num = 1 request_spec = {} @@ -109,10 +106,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_cost_fn_weights(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn', - ] - FLAGS.noop_cost_fn_weight = 2 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.noop_cost_fn'], + noop_cost_fn_weight=2) num = 1 request_spec = {} @@ -123,10 +119,9 @@ class LeastCostSchedulerTestCase(test.TestCase): self.assertWeights(expected, num, request_spec, hosts) def test_compute_fill_first_cost_fn(self): - FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.compute_fill_first_cost_fn', - ] - FLAGS.compute_fill_first_cost_fn_weight = 1 + self.flags(least_cost_scheduler_cost_functions=[ + 'nova.scheduler.least_cost.compute_fill_first_cost_fn'], + compute_fill_first_cost_fn_weight=1) num = 1 instance_type = {'memory_mb': 1024} -- cgit From 544eecd0adc2c774e512f5c364f6952fb3fd1155 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 3 Aug 2011 14:13:37 -0700 Subject: fix test_cloud FLAGS setting --- nova/tests/test_cloud.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8c1a74c70..e891fa197 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -99,11 +99,9 @@ class CloudTestCase(test.TestCase): """Makes sure describe regions runs without raising an exception""" result = self.cloud.describe_regions(self.context) self.assertEqual(len(result['regionInfo']), 1) - regions = FLAGS.region_list - FLAGS.region_list = ["one=test_host1", "two=test_host2"] + self.flags(region_list=["one=test_host1", "two=test_host2"]) result = self.cloud.describe_regions(self.context) self.assertEqual(len(result['regionInfo']), 2) - FLAGS.region_list = regions def test_describe_addresses(self): """Makes sure describe addresses runs without raising an exception""" -- cgit From 4fe63486dd44b036b87d0357563afe4396ecaeb3 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 21:43:48 +0000 Subject: Fix trailing whitespace (PEP8) --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') 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 b87a999725c90e1c80ff7c2b8102b1c5921f8bc0 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 3 Aug 2011 21:44:03 +0000 Subject: Load instance_types in downgrade method too --- .../migrate_repo/versions/036_change_flavor_id_in_migrations.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py index 7ff709c17..f3244033b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py @@ -54,6 +54,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine + instance_types = Table('instance_types', meta, autoload=True) migrations = Table('migrations', meta, autoload=True) migrations.create_column(old_flavor_id) migrations.create_column(new_flavor_id) -- cgit From bd0a2ed74764de262a828fd086fa0e08493b2cec Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 3 Aug 2011 17:39:51 -0500 Subject: Fixes lp819397 --- nova/tests/test_xenapi.py | 7 +++++++ nova/virt/xenapi/vmops.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..26a36b231 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -754,12 +754,18 @@ class XenAPIMigrateInstance(test.TestCase): def test_finish_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False + self.fake_vm_start_called = False + + def fake_vm_start(*args, **kwargs): + self.fake_vm_start_called = True def fake_vdi_resize(*args, **kwargs): self.called = True self.stubs.Set(stubs.FakeSessionForMigrationTests, "VDI_resize_online", fake_vdi_resize) + self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) conn = xenapi_conn.get_connection(False) @@ -781,6 +787,7 @@ class XenAPIMigrateInstance(test.TestCase): dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) self.assertEqual(self.called, True) + self.assertEqual(self.fake_vm_start_called, True) def test_finish_migrate_no_local_storage(self): tiny_type_id = \ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b3b812a48..3b9f35034 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -122,7 +122,7 @@ class VMOps(object): network_info) if resize_instance: self.resize_instance(instance, vdi_uuid) - self._spawn(instance, vm_ref) + self._start(instance, vm_ref=vm_ref) def _start(self, instance, vm_ref=None): """Power on a VM instance""" -- cgit From f34a6fb9efd8d950555431f5e7268dbde8ae78c8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 19:17:08 -0400 Subject: Remove instances of the "diaper pattern" Anywhere "except:" occurs, I tried to replace it with an explicit except on known error types. If none were known, Except was used. In the process I've been able to unearth a few evasive bugs and clean up some adjacent code. --- nova/api/direct.py | 5 ++--- nova/api/ec2/__init__.py | 4 ++-- nova/api/ec2/apirequest.py | 2 +- nova/api/openstack/common.py | 27 +++++++++++---------------- nova/api/openstack/servers.py | 22 +++++++++++----------- nova/cloudpipe/pipelib.py | 17 +++++++---------- nova/db/sqlalchemy/api.py | 14 ++++++++------ nova/image/__init__.py | 3 ++- nova/utils.py | 18 +++++++++++++++++- nova/virt/libvirt/connection.py | 6 +++--- nova/virt/libvirt/vif.py | 5 +++-- nova/virt/xenapi_conn.py | 2 +- nova/vnc/proxy.py | 4 ++-- 13 files changed, 70 insertions(+), 59 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index 993815fc7..139c46d63 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,7 +48,6 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} - def register_service(path, handle): """Register a service handle at a given path. @@ -296,8 +295,8 @@ class ServiceWrapper(object): 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(), }[content_type] return serializer.serialize(result) - except: - raise exception.Error("returned non-serializable type: %s" + except Exception, e: + raise exception.Error(_("Returned non-serializable type: %s") % result) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index af232edda..804e54ef9 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -147,7 +147,7 @@ class Authenticate(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. @@ -211,7 +211,7 @@ class Requestify(wsgi.Middleware): for non_arg in non_args: # Remove, but raise KeyError if omitted args.pop(non_arg) - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() LOG.debug(_('action: %s'), action) diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 7d78c5cfa..9a3e55925 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -104,7 +104,7 @@ class APIRequest(object): for key in data.keys(): val = data[key] el.appendChild(self._render_data(xml, key, val)) - except: + except Exception: LOG.debug(data) raise diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..1304379a8 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -16,7 +16,7 @@ # under the License. import re -from urlparse import urlparse +import urlparse from xml.dom import minidom import webob @@ -137,8 +137,8 @@ def get_id_from_href(href): if re.match(r'\d+$', str(href)): return int(href) try: - return int(urlparse(href).path.split('/')[-1]) - except: + return int(urlparse.urlsplit(href).path.split('/')[-1]) + except ValueError, e: LOG.debug(_("Error extracting id from href: %s") % href) raise ValueError(_('could not parse id from href')) @@ -153,22 +153,17 @@ def remove_version_from_href(href): Returns: 'http://www.nova.com' """ - try: - #removes the first instance that matches /v#.#/ - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1) + parsed_url = urlparse.urlsplit(href) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) - #if no version was found, try finding /v#.# at the end of the string - if new_href == href: - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1) - except: - LOG.debug(_("Error removing version from href: %s") % href) - msg = _('could not parse version from href') + if new_path == parsed_url.path: + msg = _('href %s does not contain version') % href + LOG.debug(msg) raise ValueError(msg) - if new_href == href: - msg = _('href does not contain version') - raise ValueError(msg) - return new_href + parsed_url = list(parsed_url) + parsed_url[2] = new_path + return urlparse.urlunsplit(parsed_url) def get_version_from_href(href): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..493845e8c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -290,7 +290,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -306,7 +306,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.unlock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -321,7 +321,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.get_lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -336,7 +336,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.reset_network(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -351,7 +351,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.inject_network_info(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -363,7 +363,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.pause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -375,7 +375,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.unpause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -387,7 +387,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.suspend(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -399,7 +399,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.resume(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -420,7 +420,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.rescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -432,7 +432,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.unrescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) raise exc.HTTPUnprocessableEntity() diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 521525205..46549c9d7 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -141,15 +141,12 @@ class CloudPipe(object): try: result = cloud._gen_key(context, context.user_id, key_name) private_key = result['private_key'] - try: - key_dir = os.path.join(FLAGS.keys_path, context.user_id) - if not os.path.exists(key_dir): - os.makedirs(key_dir) - key_path = os.path.join(key_dir, '%s.pem' % key_name) - with open(key_path, 'w') as f: - f.write(private_key) - except: - pass - except exception.Duplicate: + key_dir = os.path.join(FLAGS.keys_path, context.user_id) + if not os.path.exists(key_dir): + os.makedirs(key_dir) + key_path = os.path.join(key_dir, '%s.pem' % key_name) + with open(key_path, 'w') as f: + f.write(private_key) + except exception.Duplicate, os.error, IOError: pass return key_name diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4f1445217..ce12ba4e0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3178,8 +3178,9 @@ def instance_metadata_delete_all(context, instance_id): @require_context @require_instance_exists -def instance_metadata_get_item(context, instance_id, key): - session = get_session() +def instance_metadata_get_item(context, instance_id, key, session=None): + if not session: + session = get_session() meta_result = session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ @@ -3205,7 +3206,7 @@ def instance_metadata_update_or_create(context, instance_id, metadata): try: meta_ref = instance_metadata_get_item(context, instance_id, key, session) - except: + except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() meta_ref.update({"key": key, "value": value, "instance_id": instance_id, @@ -3300,8 +3301,9 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key): - session = get_session() +def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): + if not session: + session = get_session() spec_result = session.query(models.InstanceTypeExtraSpecs).\ filter_by(instance_type_id=instance_type_id).\ @@ -3327,7 +3329,7 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id, instance_type_id, key, session) - except: + except exception.InstanceTypeExtraSpecsNotFound, e: spec_ref = models.InstanceTypeExtraSpecs() spec_ref.update({"key": key, "value": value, "instance_type_id": instance_type_id, diff --git a/nova/image/__init__.py b/nova/image/__init__.py index a27d649d4..5447c8a3a 100644 --- a/nova/image/__init__.py +++ b/nova/image/__init__.py @@ -35,6 +35,7 @@ def _parse_image_ref(image_href): :param image_href: href of an image :returns: a tuple of the form (image_id, host, port) + :raises ValueError """ o = urlparse(image_href) @@ -72,7 +73,7 @@ def get_glance_client(image_href): try: (image_id, host, port) = _parse_image_ref(image_href) - except: + except ValueError: raise exception.InvalidImageRef(image_href=image_href) glance_client = GlanceClient(host, port) return (glance_client, image_id) diff --git a/nova/utils.py b/nova/utils.py index 4ea623cc1..1e2dbebb1 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -126,6 +126,22 @@ def fetchfile(url, target): def execute(*cmd, **kwargs): + """ + Helper method to execute command with optional retry. + + :cmd Passed to subprocess.Popen. + :process_input Send to opened process. + :addl_env Added to the processes env. + :check_exit_code Defaults to 0. Raise exception.ProcessExecutionError + unless program exits with this code. + :delay_on_retry True | False. Defaults to True. If set to True, wait a + short amount of time before retrying. + :attempts How many times to retry cmd. + + :raises exception.Error on receiving unknown arguments + :raises exception.ProcessExecutionError + """ + process_input = kwargs.pop('process_input', None) addl_env = kwargs.pop('addl_env', None) check_exit_code = kwargs.pop('check_exit_code', 0) @@ -790,7 +806,7 @@ def parse_server_string(server_str): (address, port) = server_str.split(':') return (address, port) - except: + except Exception: LOG.debug(_('Invalid server_string: %s' % server_str)) return ('', '') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0acf25d28..4dd8f02e5 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -370,7 +370,7 @@ class LibvirtConnection(driver.ComputeDriver): """Returns the xml for the disk mounted at device""" try: doc = libxml2.parseDoc(xml) - except: + except Exception: return None ctx = doc.xpathNewContext() try: @@ -1103,7 +1103,7 @@ class LibvirtConnection(driver.ComputeDriver): try: doc = libxml2.parseDoc(xml) - except: + except Exception: return [] ctx = doc.xpathNewContext() @@ -1144,7 +1144,7 @@ class LibvirtConnection(driver.ComputeDriver): try: doc = libxml2.parseDoc(xml) - except: + except Exception: return [] ctx = doc.xpathNewContext() diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index eef582fac..4be60ce8a 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -25,6 +25,7 @@ from nova.network import linux_net from nova.virt.libvirt import netutils from nova import utils from nova.virt.vif import VIFDriver +from nova import exception LOG = logging.getLogger('nova.virt.libvirt.vif') @@ -128,7 +129,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): utils.execute('sudo', 'ovs-vsctl', 'del-port', network['bridge'], dev) utils.execute('sudo', 'ip', 'link', 'delete', dev) - except: + except exception.ProcessExecutionError, e: LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) - raise + raise e diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..f75d4493c 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -441,7 +441,7 @@ class XenAPISession(object): params = None try: params = eval(exc.details[3]) - except: + except Exception: raise exc raise self.XenAPI.Failure(params) else: diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index c4603803b..2e3e38ca9 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -60,7 +60,7 @@ class WebsocketVNCProxy(object): break d = base64.b64encode(d) dest.send(d) - except: + except Exception: source.close() dest.close() @@ -72,7 +72,7 @@ class WebsocketVNCProxy(object): break d = base64.b64decode(d) dest.sendall(d) - except: + except Exception: source.close() dest.close() -- cgit From 23f6ec84a93638e880f60bec00db8587b9e3e8d8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 20:26:37 -0400 Subject: fix pylint W0102 errors. --- nova/api/openstack/extensions.py | 6 +++++- nova/compute/api.py | 19 +++++++++++++++---- nova/objectstore/s3server.py | 5 ++++- nova/scheduler/manager.py | 4 +++- nova/tests/api/openstack/test_limits.py | 5 ++++- nova/tests/test_auth.py | 7 ++++++- nova/tests/test_compute.py | 10 ++++++++-- nova/tests/test_libvirt.py | 5 ++++- nova/virt/vmwareapi/io_util.py | 5 ++++- nova/virt/vmwareapi/vim_util.py | 10 ++++++++-- nova/virt/vmwareapi/vmware_images.py | 6 +++++- 11 files changed, 66 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..b070dd61a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -460,7 +460,11 @@ class ResourceExtension(object): """Add top level resources to the OpenStack API in nova.""" def __init__(self, collection, controller, parent=None, - collection_actions={}, member_actions={}): + collection_actions=None, member_actions=None): + if not collection_actions: + collection_actions = {} + if not member_actions: + member_actions = {} self.collection = collection self.controller = controller self.parent = parent diff --git a/nova/compute/api.py b/nova/compute/api.py index aae16d1da..fe62b50ce 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -121,8 +121,10 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def _check_metadata_properties_quota(self, context, metadata={}): + def _check_metadata_properties_quota(self, context, metadata=None): """Enforce quota limits on metadata properties.""" + if not metadata: + metadata = {} num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: @@ -148,7 +150,7 @@ class API(base.Base): min_count=None, max_count=None, display_name='', display_description='', key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata={}, + availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, reservation_id=None): """Verify all the input parameters regardless of the provisioning @@ -160,6 +162,8 @@ class API(base.Base): min_count = 1 if not max_count: max_count = min_count + if not metadata: + metadata = {} num_instances = quota.allowed_instances(context, max_count, instance_type) @@ -395,12 +399,16 @@ class API(base.Base): min_count=None, max_count=None, display_name='', display_description='', key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata={}, + availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, reservation_id=None, block_device_mapping=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" + + if not metadata: + metadata = {} + num_instances, base_options, image = self._check_create_parameters( context, instance_type, image_href, kernel_id, ramdisk_id, @@ -424,7 +432,7 @@ class API(base.Base): min_count=None, max_count=None, display_name='', display_description='', key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata={}, + availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, reservation_id=None, block_device_mapping=None): """ @@ -439,6 +447,9 @@ class API(base.Base): Returns a list of instance dicts. """ + if not metadata: + metadata = {} + num_instances, base_options, image = self._check_create_parameters( context, instance_type, image_href, kernel_id, ramdisk_id, diff --git a/nova/objectstore/s3server.py b/nova/objectstore/s3server.py index 76025a1e3..1ab47b034 100644 --- a/nova/objectstore/s3server.py +++ b/nova/objectstore/s3server.py @@ -155,7 +155,10 @@ class BaseRequestHandler(object): self.finish('\n' + ''.join(parts)) - def _render_parts(self, value, parts=[]): + def _render_parts(self, value, parts=None): + if not parts: + parts = [] + if isinstance(value, basestring): parts.append(utils.xhtml_escape(value)) elif isinstance(value, int) or isinstance(value, long): diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 749d66cad..c8b16b622 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -69,8 +69,10 @@ class SchedulerManager(manager.Manager): return self.zone_manager.get_zone_capabilities(context) def update_service_capabilities(self, context=None, service_name=None, - host=None, capabilities={}): + host=None, capabilities=None): """Process a capability update from a service node.""" + if not capability: + capability = {} self.zone_manager.update_service_capabilities(service_name, host, capabilities) diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 6c3d531e3..1dc3c3a17 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -819,12 +819,15 @@ class FakeHttplibConnection(object): self.app = app self.host = host - def request(self, method, path, body="", headers={}): + def request(self, method, path, body="", headers=None): """ Requests made via this connection actually get translated and routed into our WSGI app, we then wait for the response and turn it back into an `httplib.HTTPResponse`. """ + if not headers: + headers = {} + req = webob.Request.blank(path) req.method = method req.headers = headers diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 7c0f783bb..0fb2fdcc6 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -62,7 +62,12 @@ class project_generator(object): class user_and_project_generator(object): - def __init__(self, manager, user_state={}, project_state={}): + def __init__(self, manager, user_state=None, project_state=None): + if not user_state: + user_state = {} + if not project_state: + project_state = {} + self.manager = manager if 'name' not in user_state: user_state['name'] = 'test1' diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 879e4b9cb..9be349d14 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -73,8 +73,11 @@ class ComputeTestCase(test.TestCase): self.stubs.Set(nova.image.fake._FakeImageService, 'show', fake_show) - def _create_instance(self, params={}): + def _create_instance(self, params=None): """Create a test instance""" + if not params: + params = {} + inst = {} inst['image_ref'] = 1 inst['reservation_id'] = 'r-fakeres' @@ -87,8 +90,11 @@ class ComputeTestCase(test.TestCase): inst.update(params) return db.instance_create(self.context, inst)['id'] - def _create_instance_type(self, params={}): + def _create_instance_type(self, params=None): """Create a test instance""" + if not params: + params = {} + context = self.context.elevated() inst = {} inst['name'] = 'm1.small' diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index c5c3151dc..aa77258cb 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -1162,8 +1162,11 @@ class NWFilterTestCase(test.TestCase): 'project_id': 'fake', 'instance_type_id': 1}) - def _create_instance_type(self, params={}): + def _create_instance_type(self, params=None): """Create a test instance""" + if not params: + params = {} + context = self.context.elevated() inst = {} inst['name'] = 'm1.small' diff --git a/nova/virt/vmwareapi/io_util.py b/nova/virt/vmwareapi/io_util.py index 2ec773b7b..409242800 100644 --- a/nova/virt/vmwareapi/io_util.py +++ b/nova/virt/vmwareapi/io_util.py @@ -68,7 +68,10 @@ class GlanceWriteThread(object): """Ensures that image data is written to in the glance client and that it is in correct ('active')state.""" - def __init__(self, input, glance_client, image_id, image_meta={}): + def __init__(self, input, glance_client, image_id, image_meta=None): + if not image_meta: + image_meta = {} + self.input = input self.glance_client = glance_client self.image_id = image_id diff --git a/nova/virt/vmwareapi/vim_util.py b/nova/virt/vmwareapi/vim_util.py index 11214231c..e03daddac 100644 --- a/nova/virt/vmwareapi/vim_util.py +++ b/nova/virt/vmwareapi/vim_util.py @@ -95,9 +95,12 @@ def build_recursive_traversal_spec(client_factory): def build_property_spec(client_factory, type="VirtualMachine", - properties_to_collect=["name"], + properties_to_collect=None, all_properties=False): """Builds the Property Spec.""" + if not properties_to_collect: + properties_to_collect = ["name"] + property_spec = client_factory.create('ns0:PropertySpec') property_spec.all = all_properties property_spec.pathSet = properties_to_collect @@ -155,8 +158,11 @@ def get_dynamic_property(vim, mobj, type, property_name): return property_value -def get_objects(vim, type, properties_to_collect=["name"], all=False): +def get_objects(vim, type, properties_to_collect=None, all=False): """Gets the list of objects of the type specified.""" + if not properties_to_collect: + properties_to_collect = ["name"] + client_factory = vim.client.factory object_spec = build_object_spec(client_factory, vim.get_service_content().rootFolder, diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py index 70adba74f..f5f75dae2 100644 --- a/nova/virt/vmwareapi/vmware_images.py +++ b/nova/virt/vmwareapi/vmware_images.py @@ -33,11 +33,15 @@ QUEUE_BUFFER_SIZE = 10 def start_transfer(read_file_handle, data_size, write_file_handle=None, - glance_client=None, image_id=None, image_meta={}): + glance_client=None, image_id=None, image_meta=None): """Start the data transfer from the reader to the writer. Reader writes to the pipe and the writer reads from the pipe. This means that the total transfer time boils down to the slower of the read/write and not the addition of the two times.""" + + if not image_meta: + image_meta = {} + # The pipe that acts as an intermediate store of data for reader to write # to and writer to grab from. thread_safe_pipe = io_util.ThreadSafePipe(QUEUE_BUFFER_SIZE, data_size) -- cgit From 27392e9412c7042b67947dbc103704d121d60e12 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Wed, 3 Aug 2011 20:48:17 -0500 Subject: Conditionals were not actually runing the tests when they were supposed to. Renamed example testcases --- nova/test.py | 2 ++ nova/tests/test_skip_examples.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index f1b4050f9..97cd3e0d0 100644 --- a/nova/test.py +++ b/nova/test.py @@ -79,6 +79,7 @@ class skip_if(object): """Wrapped skipper function.""" if self.condition: raise nose.SkipTest(self.message) + func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper @@ -95,6 +96,7 @@ class skip_unless(object): """Wrapped skipper function.""" if not self.condition: raise nose.SkipTest(self.message) + func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 41c3b1b5f..0ae3576f7 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -21,13 +21,13 @@ from nova import test class ExampleSkipTestCase(test.TestCase): @test.skip_test("Example usage of @test.skip_test()") - def test_skip_test(self): + def test_skip_test_example(self): self.fail("skip_test failed to work properly.") @test.skip_if(True, "Example usage of @test.skip_if()") - def test_skip_if_env_user_exists(self): + def test_skip_if_example(self): self.fail("skip_if failed to work properly.") @test.skip_unless(False, "Example usage of @test.skip_unless()") - def test_skip_unless_env_foo_exists(self): + def test_skip_unless_example(self): self.fail("skip_unless failed to work properly.") -- cgit From 6f3b63cd51549843b269cc6c138575a5af2337b2 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 4 Aug 2011 08:43:13 +0000 Subject: Revert should be sent to destination node and confirm should be sent to source node --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index aae16d1da..1d09c69f6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -888,7 +888,7 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, instance_ref['uuid'], - migration_ref['source_compute'], + migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], @@ -908,7 +908,7 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, instance_ref['uuid'], - migration_ref['dest_compute'], + migration_ref['source_compute'], params=params) self.db.migration_update(context, migration_ref['id'], -- cgit From 144720ce87b3f0f469ed748780af55e32babc821 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 09:29:43 -0500 Subject: Added in tests that verify tests are skipped appropriately. --- nova/tests/test_skip_examples.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 0ae3576f7..033f1ea2e 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -20,6 +20,8 @@ from nova import test class ExampleSkipTestCase(test.TestCase): + test_counter = 0 + @test.skip_test("Example usage of @test.skip_test()") def test_skip_test_example(self): self.fail("skip_test failed to work properly.") @@ -31,3 +33,15 @@ class ExampleSkipTestCase(test.TestCase): @test.skip_unless(False, "Example usage of @test.skip_unless()") def test_skip_unless_example(self): self.fail("skip_unless failed to work properly.") + + @test.skip_if(False, "This test case should never be skipped.") + def test_001_increase_test_counter(self): + ExampleSkipTestCase.test_counter += 1 + + @test.skip_unless(True, "This test case should never be skipped.") + def test_002_increase_test_counter(self): + ExampleSkipTestCase.test_counter += 1 + + def test_003_verify_test_counter(self): + self.assertEquals(ExampleSkipTestCase.test_counter, 2, + "Tests were not skipped appropriately") -- cgit From dbbc07750c2624b61af4f824d29c99bffe5f722f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 09:40:51 -0500 Subject: Fixed rescue and unrescue. --- nova/compute/manager.py | 25 ++++++++++++------------- nova/exception.py | 3 ++- nova/virt/xenapi/vmops.py | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8780ad921..cf4ee229d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -648,38 +648,37 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def rescue_instance(self, context, instance_uuid): + def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) - LOG.audit(_('instance %s: rescuing'), instance_uuid, context=context) + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, - instance_uuid, + instance_id, power_state.NOSTATE, 'rescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_uuid, result) + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.rescue(context, instance_ref, _update_state, network_info) - self._update_state(context, instance_uuid) + self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def unrescue_instance(self, context, instance_uuid): + def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) - LOG.audit(_('instance %s: unrescuing'), instance_uuid, - context=context) + instance_ref = self.db.instance_get(context, instance_id) + LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, - instance_uuid, + instance_id, power_state.NOSTATE, 'unrescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_uuid, result) + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) self.driver.unrescue(instance_ref, _update_state, network_info) - self._update_state(context, instance_uuid) + self._update_state(context, instance_id) @staticmethod def _update_state_callback(self, context, instance_id, result): diff --git a/nova/exception.py b/nova/exception.py index 68e6ac937..8503f8e3e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -122,7 +122,8 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - raise + # Don't mask the real exception. + raise Exception(e) return wraps(f)(wrapped) return inner diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b3b812a48..9979a0165 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -935,7 +935,8 @@ class VMOps(object): self.spawn_rescue(context, instance, network_info) rescue_vm_ref = VMHelper.lookup(self._session, instance.name) - vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[0] + #NOTE(jk0): Find the root partition, not swap. + vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] rescue_vbd_ref = VMHelper.create_vbd(self._session, rescue_vm_ref, vdi_ref, 1, False) -- cgit From a3abff9c6243ecfddac72167320c74dce7398941 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 4 Aug 2011 15:02:28 +0000 Subject: Changed the definition of the 'action' dict to always occur. --- nova/virt/xenapi_conn.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..8ddbbd33a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -394,11 +394,10 @@ class XenAPISession(object): try: name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) + # Ensure action is never > 255 + action = dict(action=name[:255], error=None) if id: - action = dict( - instance_id=int(id), - action=name[0:255], # Ensure action is never > 255 - error=None) + action["instance_id"] = int(id) if status == "pending": return elif status == "success": -- cgit From 4eed25b0f01b510d0d90e864eef7f285964ab293 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 4 Aug 2011 11:11:12 -0400 Subject: The OSAPI v1.0 image create POST request should store the instance_id as a Glance property. --- nova/api/openstack/images.py | 8 ++++++-- nova/tests/api/openstack/test_images.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0834adfa5..c76738d30 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -116,13 +116,17 @@ class ControllerV10(Controller): try: image_name = image["name"] - server_id = image["serverId"] + instance_id = image["serverId"] except KeyError as missing_key: msg = _("Image entity requires %s") % missing_key raise webob.exc.HTTPBadRequest(explanation=msg) context = req.environ["nova.context"] - image = self._compute_service.snapshot(context, server_id, image_name) + props = {'instance_id': instance_id} + image = self._compute_service.snapshot(context, + instance_id, + image_name, + extra_properties=props) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 942c0b333..6486a069c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1031,6 +1031,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): req.headers["content-type"] = "application/json" response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) + image_meta = json.loads(response.body)['image'] + self.assertEqual(123, image_meta['serverId']) + self.assertEqual('Snapshot 1', image_meta['name']) def test_create_snapshot_no_name(self): """Name is required for snapshots""" -- cgit From ae6b54cc1748d7b9c7bfa55374e9355665343b82 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 4 Aug 2011 11:45:24 -0400 Subject: add test for spawning a xenapi instance with an empty dns list --- nova/tests/test_xenapi.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..5f3aec700 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -397,7 +397,7 @@ class XenAPIVMTestCase(test.TestCase): instance_type_id="3", os_type="linux", architecture="x86-64", instance_id=1, check_injection=False, - create_record=True): + create_record=True, empty_dns=False): stubs.stubout_loopingcall_start(self.stubs) if create_record: values = {'id': instance_id, @@ -426,12 +426,22 @@ class XenAPIVMTestCase(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] + if empty_dns: + network_info[0][1]['dns'] = [] + self.conn.spawn(self.context, instance, network_info) self.create_vm_record(self.conn, os_type, instance_id) self.check_vm_record(self.conn, check_injection) self.assertTrue(instance.os_type) self.assertTrue(instance.architecture) + def test_spawn_empty_dns(self): + """"Test spawning with an empty dns list""" + self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, + os_type="linux", architecture="x86-64", + empty_dns=True) + self.check_vm_params_for_linux() + def test_spawn_not_enough_memory(self): self.assertRaises(Exception, self._test_spawn, -- cgit From 5826a793e7e05db8ccc14d15326245246fb652d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 4 Aug 2011 12:48:13 -0400 Subject: fixing typo --- nova/api/openstack/create_instance_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 178685889..2a8e7fd7e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -332,7 +332,8 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - data['metadata'] = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) + data['metadata'] = metadata return data def create(self, string): -- cgit From 8fd335e309ea48d1460ee4d04105e429575f47f6 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:03:15 -0700 Subject: fix up new test_server_actions.py file for flags verbosity change --- nova/tests/api/openstack/test_server_actions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fc08a2d53..baec5f3d1 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -8,7 +8,6 @@ import webob from nova import context from nova import db -from nova import flags from nova import utils from nova.api.openstack import create_instance_helper from nova.compute import instance_types @@ -19,10 +18,6 @@ from nova.tests.api.openstack import common from nova.tests.api.openstack import fakes -FLAGS = flags.FLAGS -FLAGS.verbose = True - - def return_server_by_id(context, id): return _get_instance() @@ -102,6 +97,7 @@ class ServerActionsTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServerActionsTest, self).setUp() + self.set_flags_verbosity(True) self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} -- cgit From 24b308ebb4f7fff5520383248170bfb7d51c6755 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:17:55 -0700 Subject: test_host_filter setUp needs to call its super --- nova/tests/scheduler/test_host_filter.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index b016923c4..7e664d3f9 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -54,6 +54,7 @@ class HostFilterTestCase(test.TestCase): 'host_name-label': 'xs-%s' % multiplier} def setUp(self): + super(HostFilterTestCase, self).setUp() default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' self.flags(default_host_filter=default_host_filter) self.instance_type = dict(name='tiny', -- cgit From 7f35619377593b5bb30425984999e8bb9ed2bdb2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 10:34:36 -0700 Subject: fix pep8 issues that are in trunk --- nova/compute/manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..9f566dea7 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -167,10 +167,12 @@ class ComputeManager(manager.SchedulerDependentManager): 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) elif drv_state == power_state.RUNNING: - try: # Hyper-V and VMWareAPI drivers will raise and exception + # Hyper-V and VMWareAPI drivers will raise and exception + try: self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - LOG.warning(_('Hypervisor driver does not support firewall rules')) + LOG.warning(_('Hypervisor driver does not ' + 'support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" -- cgit From 194f0e4909490c4b626bd211c46121ae37db20dd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 10:43:42 -0700 Subject: uses 2.6.0 novaclient (OS API 1.1 support) --- nova/compute/manager.py | 5 +++-- nova/scheduler/api.py | 13 +++++++------ nova/scheduler/zone_aware_scheduler.py | 9 +++++---- nova/scheduler/zone_manager.py | 7 ++++--- nova/tests/scheduler/test_scheduler.py | 12 +++++++----- nova/tests/test_zones.py | 1 - 6 files changed, 26 insertions(+), 21 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..843f6d490 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -167,10 +167,11 @@ class ComputeManager(manager.SchedulerDependentManager): 'nova-compute restart.'), locals()) self.reboot_instance(context, instance['id']) elif drv_state == power_state.RUNNING: - try: # Hyper-V and VMWareAPI drivers will raise and exception + try: # Hyper-V and VMWareAPI drivers will raise and exception self.driver.ensure_filtering_rules_for_instance(instance) except NotImplementedError: - LOG.warning(_('Hypervisor driver does not support firewall rules')) + LOG.warning( + _('Hypervisor driver does not support firewall rules')) def _update_state(self, context, instance_id, state=None): """Update the state of an instance from the driver info.""" diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 137b671c0..55cea5f8f 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -17,7 +17,8 @@ Handles all requests relating to schedulers. """ -import novaclient +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions from nova import db from nova import exception @@ -112,7 +113,7 @@ def _wrap_method(function, self): def _process(func, zone): """Worker stub for green thread pool. Give the worker an authenticated nova client and zone info.""" - nova = novaclient.OpenStack(zone.username, zone.password, None, + nova = novaclient.Client(zone.username, zone.password, None, zone.api_url) nova.authenticate() return func(nova, zone) @@ -132,10 +133,10 @@ def call_zone_method(context, method_name, errors_to_ignore=None, zones = db.zone_get_all(context) for zone in zones: try: - nova = novaclient.OpenStack(zone.username, zone.password, None, + nova = novaclient.Client(zone.username, zone.password, None, zone.api_url) nova.authenticate() - except novaclient.exceptions.BadRequest, e: + except novaclient_exceptions.BadRequest, e: url = zone.api_url LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") % locals()) @@ -188,7 +189,7 @@ def _issue_novaclient_command(nova, zone, collection, if method_name in ['find', 'findall']: try: return getattr(manager, method_name)(**kwargs) - except novaclient.NotFound: + except novaclient_exceptions.NotFound: url = zone.api_url LOG.debug(_("%(collection)s.%(method_name)s didn't find " "anything matching '%(kwargs)s' on '%(url)s'" % @@ -200,7 +201,7 @@ def _issue_novaclient_command(nova, zone, collection, item = args.pop(0) try: result = manager.get(item) - except novaclient.NotFound: + except novaclient_exceptions.NotFound: url = zone.api_url LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" % locals())) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d99d7214c..7e813af7e 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -24,7 +24,9 @@ import operator import json import M2Crypto -import novaclient + +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions from nova import crypto from nova import db @@ -117,10 +119,9 @@ class ZoneAwareScheduler(driver.Scheduler): % locals()) nova = None try: - nova = novaclient.OpenStack(zone.username, zone.password, None, - url) + nova = novaclient.Client(zone.username, zone.password, None, url) nova.authenticate() - except novaclient.exceptions.BadRequest, e: + except novaclient_exceptions.BadRequest, e: raise exception.NotAuthorized(_("Bad credentials attempting " "to talk to zone at %(url)s.") % locals()) diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index efdac06e1..97bdf3d44 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -18,10 +18,11 @@ ZoneManager oversees all communications with child Zones. """ import datetime -import novaclient import thread import traceback +from novaclient import v1_1 as novaclient + from eventlet import greenpool from nova import db @@ -89,8 +90,8 @@ class ZoneState(object): def _call_novaclient(zone): """Call novaclient. Broken out for testing purposes.""" - client = novaclient.OpenStack(zone.username, zone.password, None, - zone.api_url) + client = novaclient.Client(zone.username, zone.password, None, + zone.api_url) return client.zones.info()._info diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 6a56a57db..bd2394adf 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -21,9 +21,11 @@ Tests For Scheduler import datetime import mox -import novaclient.exceptions import stubout +from novaclient import v1_1 as novaclient +from novaclient import exceptions as novaclient_exceptions + from mox import IgnoreArg from nova import context from nova import db @@ -1039,10 +1041,10 @@ class FakeServerCollection(object): class FakeEmptyServerCollection(object): def get(self, f): - raise novaclient.NotFound(1) + raise novaclient_exceptions.NotFound(1) def find(self, name): - raise novaclient.NotFound(2) + raise novaclient_exceptions.NotFound(2) class FakeNovaClient(object): @@ -1088,7 +1090,7 @@ class FakeZonesProxy(object): raise Exception('testing') -class FakeNovaClientOpenStack(object): +class FakeNovaClientZones(object): def __init__(self, *args, **kwargs): self.zones = FakeZonesProxy() @@ -1101,7 +1103,7 @@ class CallZoneMethodTest(test.TestCase): super(CallZoneMethodTest, self).setUp() self.stubs = stubout.StubOutForTesting() self.stubs.Set(db, 'zone_get_all', zone_get_all) - self.stubs.Set(novaclient, 'OpenStack', FakeNovaClientOpenStack) + self.stubs.Set(novaclient, 'Client', FakeNovaClientZones) def tearDown(self): self.stubs.UnsetAll() diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py index a943fee27..9efa23015 100644 --- a/nova/tests/test_zones.py +++ b/nova/tests/test_zones.py @@ -18,7 +18,6 @@ Tests For ZoneManager import datetime import mox -import novaclient from nova import context from nova import db -- cgit From 7e70e5ff7c39862a328b304f13778936e422a212 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 4 Aug 2011 14:37:24 -0400 Subject: remove unused reference to exception object --- nova/virt/libvirt/vif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 4be60ce8a..711b05bae 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -129,7 +129,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): utils.execute('sudo', 'ovs-vsctl', 'del-port', network['bridge'], dev) utils.execute('sudo', 'ip', 'link', 'delete', dev) - except exception.ProcessExecutionError, e: + except exception.ProcessExecutionError: LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) - raise e + raise -- cgit From 7381c55a3549ead494c6bd13dece17f293442940 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 11:40:07 -0700 Subject: added NOVA_VERSION to novarc --- nova/auth/novarc.template | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index d05c099d7..978ffb210 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -16,3 +16,4 @@ export NOVA_API_KEY="%(access)s" export NOVA_USERNAME="%(user)s" export NOVA_PROJECT_ID="%(project)s" export NOVA_URL="%(os)s" +export NOVA_VERSION="1.1" -- cgit From bdbf3efcadeda46e66787edee344def84dccef73 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 4 Aug 2011 11:50:20 -0700 Subject: OS v1.1 is now the default into novarc --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 12c6d1356..eb6366ed9 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -317,7 +317,7 @@ DEFINE_string('osapi_extensions_path', '/var/lib/nova/extensions', DEFINE_string('osapi_host', '$my_ip', 'ip of api server') DEFINE_string('osapi_scheme', 'http', 'prefix for openstack') DEFINE_integer('osapi_port', 8774, 'OpenStack API port') -DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack') +DEFINE_string('osapi_path', '/v1.1/', 'suffix for openstack') DEFINE_integer('osapi_max_limit', 1000, 'max number of items returned in a collection response') -- cgit From 7f35b6ee93ea2192a8c81a7c17e2e74694a397a1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 12:07:07 -0700 Subject: remove set_flags_verbosity.. it's not needed --- nova/test.py | 5 ----- nova/tests/api/openstack/test_accounts.py | 3 +-- nova/tests/api/openstack/test_adminapi.py | 2 +- nova/tests/api/openstack/test_server_actions.py | 2 +- nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/api/openstack/test_users.py | 3 +-- nova/tests/api/openstack/test_zones.py | 3 +-- nova/tests/integrated/integrated_helpers.py | 2 +- 8 files changed, 7 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index fa5a662fd..ff85deeda 100644 --- a/nova/test.py +++ b/nova/test.py @@ -156,11 +156,6 @@ class TestCase(unittest.TestCase): FLAGS.Reset() for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) - FLAGS.verbose = self._original_flags_verbosity - - def set_flags_verbosity(self, verbose=True): - """Set the FLAGS verbosity for a test""" - FLAGS.verbose = verbose def start_service(self, name, host=None, **kwargs): host = host and host or uuid.uuid4().hex diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py index b1ca546d2..707a2599f 100644 --- a/nova/tests/api/openstack/test_accounts.py +++ b/nova/tests/api/openstack/test_accounts.py @@ -35,8 +35,7 @@ def fake_admin_check(self, req): class AccountsTest(test.TestCase): def setUp(self): super(AccountsTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) self.stubs.Set(accounts.Controller, '__init__', fake_init) self.stubs.Set(accounts.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index 9d03a7f9d..c9e66dc4c 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -28,7 +28,7 @@ class AdminAPITest(test.TestCase): super(AdminAPITest, self).setUp() fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) - self.set_flags_verbosity(True) + self.flags(verbose=True) def test_admin_enabled(self): self.flags(allow_admin_api=True) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index baec5f3d1..562cefe90 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -97,7 +97,7 @@ class ServerActionsTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServerActionsTest, self).setUp() - self.set_flags_verbosity(True) + self.flags(verbose=True) self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index bb411af10..0477f6d92 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -230,7 +230,7 @@ class ServersTest(test.TestCase): def setUp(self): self.maxDiff = None super(ServersTest, self).setUp() - self.set_flags_verbosity(True) + self.flags(verbose=True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py index 649538f11..1d133f9ab 100644 --- a/nova/tests/api/openstack/test_users.py +++ b/nova/tests/api/openstack/test_users.py @@ -35,8 +35,7 @@ def fake_admin_check(self, req): class UsersTest(test.TestCase): def setUp(self): super(UsersTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) self.stubs.Set(users.Controller, '__init__', fake_init) self.stubs.Set(users.Controller, '_check_admin', diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py index 442ee4313..4a46a5764 100644 --- a/nova/tests/api/openstack/test_zones.py +++ b/nova/tests/api/openstack/test_zones.py @@ -94,8 +94,7 @@ def zone_select(context, specs): class ZonesTest(test.TestCase): def setUp(self): super(ZonesTest, self).setUp() - self.flags(allow_admin_api=True) - self.set_flags_verbosity(True) + self.flags(verbose=True, allow_admin_api=True) fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 049ddbc60..fb2f88502 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -148,7 +148,7 @@ class _IntegratedTestBase(test.TestCase): f = self._get_flags() self.flags(**f) - self.set_flags_verbosity(True) + self.flags(verbose=True) def fake_get_image_service(image_href): image_id = int(str(image_href).split('/')[-1]) -- cgit From b02fb7f6a3c35673122d7f409d8c999cc9fbae46 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 4 Aug 2011 12:21:09 -0700 Subject: remove storing original flags verbosity --- nova/test.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index ff85deeda..5760d7a82 100644 --- a/nova/test.py +++ b/nova/test.py @@ -100,7 +100,6 @@ class TestCase(unittest.TestCase): self.injected = [] self._services = [] self._original_flags = FLAGS.FlagValuesDict() - self._original_flags_verbosity = FLAGS.verbose def tearDown(self): """Runs after each test method to tear down test environment.""" -- cgit From cae90fb1d5a798673416e51c5cf1f7db6cbd6c23 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 14:30:12 -0500 Subject: Fixed rescue and unrescue. --- nova/compute/manager.py | 10 ++++------ nova/virt/xenapi/vmops.py | 4 ++-- nova/virt/xenapi_conn.py | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index cf4ee229d..c6af0dfbe 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -651,16 +651,15 @@ class ComputeManager(manager.SchedulerDependentManager): def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() + self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'rescuing') - _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.rescue(context, instance_ref, _update_state, network_info) + self.driver.rescue(context, instance_ref, None, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -668,16 +667,15 @@ class ComputeManager(manager.SchedulerDependentManager): def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() + self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'unrescuing') - _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.unrescue(instance_ref, _update_state, network_info) + self.driver.unrescue(instance_ref, None, network_info) self._update_state(context, instance_id) @staticmethod diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9979a0165..5badbac31 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -914,7 +914,7 @@ class VMOps(object): True) self._wait_with_callback(instance.id, task, callback) - def rescue(self, context, instance, callback, network_info): + def rescue(self, context, instance, _callback, network_info): """Rescue the specified instance. - shutdown the instance VM. @@ -943,7 +943,7 @@ class VMOps(object): self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref) - def unrescue(self, instance, callback): + def unrescue(self, instance, _callback): """Unrescue the specified instance. - unplug the instance VM's disk from the rescue VM. diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 39afbd650..9dcbb3cf9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -242,13 +242,13 @@ class XenAPIConnection(driver.ComputeDriver): """resume the specified instance""" self._vmops.resume(instance, callback) - def rescue(self, context, instance, callback, network_info): + def rescue(self, context, instance, _callback, network_info): """Rescue the specified instance""" - self._vmops.rescue(context, instance, callback, network_info) + self._vmops.rescue(context, instance, _callback, network_info) - def unrescue(self, instance, callback, network_info): + def unrescue(self, instance, _callback, network_info): """Unrescue the specified instance""" - self._vmops.unrescue(instance, callback) + self._vmops.unrescue(instance, _callback) def poll_rescued_instances(self, timeout): """Poll for rescued instances""" -- cgit From c56d20383a432aa2c83e7e3c2beebdb49cbe9efc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 4 Aug 2011 15:30:39 -0400 Subject: fix syntax error --- nova/cloudpipe/pipelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 46549c9d7..2c4673f9e 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -147,6 +147,6 @@ class CloudPipe(object): key_path = os.path.join(key_dir, '%s.pem' % key_name) with open(key_path, 'w') as f: f.write(private_key) - except exception.Duplicate, os.error, IOError: + except (exception.Duplicate, os.error, IOError): pass return key_name -- cgit From 12404f486a825dc8afdc45db0b10347a3a782e6d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 14:41:54 -0500 Subject: Removed temporary debugging raise. --- nova/exception.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index 8503f8e3e..68e6ac937 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -122,8 +122,7 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - # Don't mask the real exception. - raise Exception(e) + raise return wraps(f)(wrapped) return inner -- cgit From f22c19c6f78451074c33fe8da855755574cb6b49 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 15:51:41 -0400 Subject: Split serverXMLDeserializers into v1.0 and v1.1 --- nova/api/openstack/create_instance_helper.py | 48 +++++++++++++++++++++++++ nova/api/openstack/servers.py | 7 +++- nova/tests/api/openstack/test_server_actions.py | 4 +-- nova/tests/api/openstack/test_servers.py | 2 +- 4 files changed, 57 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2a8e7fd7e..832c890b6 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -304,6 +304,54 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'body': {'server': server}} + + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self.find_first_child_named(node, 'server') + + attributes = ["name", "imageId", "flavorId", "adminPass"] + for attr in attributes: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + + metadata_node = self.find_first_child_named(server_node, "metadata") + server["metadata"] = self.metadata_deserializer.extract_metadata( + metadata_node) + + server["personality"] = self._extract_personality(server_node) + + return server + + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + node = self.find_first_child_named(server_node, "personality") + personality = [] + if node is not None: + for file_node in self.find_children_named(node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) + return personality + + +class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + metadata_deserializer = common.MetadataXMLDeserializer() + def action(self, string): dom = minidom.parseString(string) action_node = dom.childNodes[0] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d17714371..3acc4510e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -891,8 +891,13 @@ def create_resource(version='1.0'): 'application/xml': xml_serializer, } + xml_deserializer = { + '1.0': helper.ServerXMLDeserializer(), + '1.1': helper.ServerXMLDeserializerV11(), + }[version] + body_deserializers = { - 'application/xml': helper.ServerXMLDeserializer(), + 'application/xml': xml_deserializer, } serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fc08a2d53..8f4cfacf8 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -734,10 +734,10 @@ class ServerActionsTestV11(test.TestCase): self.assertTrue(response.headers['Location']) -class TestServerActionXMLDeserializer(test.TestCase): +class TestServerActionXMLDeserializerV11(test.TestCase): def setUp(self): - self.deserializer = create_instance_helper.ServerXMLDeserializer() + self.deserializer = create_instance_helper.ServerXMLDeserializerV11() def tearDown(self): pass diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1349b289d..748573cce 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2181,7 +2181,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): def setUp(self): super(TestServerCreateRequestXMLDeserializerV11, self).setUp() - self.deserializer = create_instance_helper.ServerXMLDeserializer() + self.deserializer = create_instance_helper.ServerXMLDeserializerV11() def test_minimal_request(self): serial_request = """ -- cgit From b8d4004e9de28dae47c6a1772a0caac965f7a69f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:16:42 -0500 Subject: Fixed rescue unit tests. --- nova/tests/test_xenapi.py | 6 ++++++ nova/virt/xenapi/vmops.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a795b3c74..1bc55027a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -170,6 +170,10 @@ def reset_network(*args): pass +def _find_rescue_vbd_ref(*args): + pass + + class XenAPIVMTestCase(test.TestCase): """Unit tests for VM operations.""" def setUp(self): @@ -189,6 +193,8 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_stream_disk(self.stubs) stubs.stubout_is_vdi_pv(self.stubs) self.stubs.Set(vmops.VMOps, 'reset_network', reset_network) + self.stubs.Set(vmops.VMOps, '_find_rescue_vbd_ref', + _find_rescue_vbd_ref) stubs.stub_out_vm_methods(self.stubs) glance_stubs.stubout_glance_client(self.stubs) fake_utils.stub_out_utils_execute(self.stubs) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5badbac31..81c6c6967 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -743,6 +743,14 @@ class VMOps(object): except self.XenAPI.Failure, exc: LOG.exception(exc) + def _find_rescue_vbd_ref(self, vm_ref, rescue_vm_ref): + """Find and return the rescue VM's vbd_ref.""" + vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] + vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] + + return VMHelper.create_vbd(self._session, rescue_vm_ref, vdi_ref, 1, + False) + def _shutdown_rescue(self, rescue_vm_ref): """Shutdown a rescue instance.""" self._session.call_xenapi("Async.VM.hard_shutdown", rescue_vm_ref) @@ -934,12 +942,7 @@ class VMOps(object): instance._rescue = True self.spawn_rescue(context, instance, network_info) rescue_vm_ref = VMHelper.lookup(self._session, instance.name) - - #NOTE(jk0): Find the root partition, not swap. - vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] - vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] - rescue_vbd_ref = VMHelper.create_vbd(self._session, rescue_vm_ref, - vdi_ref, 1, False) + rescue_vbd_ref = self._find_rescue_vbd_ref(vm_ref, rescue_vm_ref) self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref) -- cgit From 9ce80fc74b3ea4513369b795d1e6891d6dfa8e03 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:20:37 -0400 Subject: Updated create image server action to respect 1.1 --- nova/api/openstack/create_instance_helper.py | 23 ++++++++++++++--------- nova/tests/api/openstack/test_server_actions.py | 1 - nova/tests/api/openstack/test_servers.py | 10 ---------- 3 files changed, 14 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 832c890b6..eec861428 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -380,8 +380,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - metadata = self.metadata_deserializer.extract_metadata(metadata_node) - data['metadata'] = metadata + if metadata_node is not None: + metadata = self.metadata_deserializer.extract_metadata( + metadata_node) + data['metadata'] = metadata return data def create(self, string): @@ -395,29 +397,32 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageId", "flavorId", "imageRef", - "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - server["metadata"] = self.metadata_deserializer.extract_metadata( - metadata_node) + if metadata_node is not None: + server["metadata"] = self.extract_metadata(metadata_node) - server["personality"] = self._extract_personality(server_node) + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality return server def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" node = self.find_first_child_named(server_node, "personality") - personality = [] if node is not None: + personality = [] for file_node in self.find_children_named(node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") item["contents"] = self.extract_text(file_node) personality.append(item) - return personality + return personality + else: + return None diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 8f4cfacf8..fe627987f 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -750,7 +750,6 @@ class TestServerActionXMLDeserializerV11(test.TestCase): expected = { "createImage": { "name": "new-server-test", - "metadata": {}, }, } self.assertEquals(request['body'], expected) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 748573cce..235554afb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2195,8 +2195,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "2", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2215,8 +2213,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "imageRef": "1", "flavorRef": "2", "adminPass": "1234", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2233,8 +2229,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "http://localhost:8774/v1.1/images/2", "flavorRef": "3", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2251,8 +2245,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "http://localhost:8774/v1.1/flavors/3", - "metadata": {}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2296,7 +2288,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "imageRef": "1", "flavorRef": "2", "metadata": {"one": "two", "open": "snack"}, - "personality": [], }, } self.assertEquals(request['body'], expected) @@ -2318,7 +2309,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "2", - "metadata": {}, "personality": [ {"path": "/etc/banner.txt", "contents": "MQ=="}, {"path": "/etc/hosts", "contents": "Mg=="}, -- cgit From eca23b1ad18a8626dd68cded4d488ca1aa779f69 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:22:36 -0500 Subject: Removed debugging code. --- nova/compute/manager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c6af0dfbe..cf4ee229d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -651,15 +651,16 @@ class ComputeManager(manager.SchedulerDependentManager): def rescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: rescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'rescuing') + _update_state = lambda result: self._update_state_callback( + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.rescue(context, instance_ref, None, network_info) + self.driver.rescue(context, instance_ref, _update_state, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -667,15 +668,16 @@ class ComputeManager(manager.SchedulerDependentManager): def unrescue_instance(self, context, instance_id): """Rescue an instance on this host.""" context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'unrescuing') + _update_state = lambda result: self._update_state_callback( + self, context, instance_id, result) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.unrescue(instance_ref, None, network_info) + self.driver.unrescue(instance_ref, _update_state, network_info) self._update_state(context, instance_id) @staticmethod -- cgit From 0f515d6d31b2c95ed7f1e3ca8d9d67f98fda9fbe Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:26:31 -0400 Subject: Added XML serialization for server actions --- nova/api/openstack/create_instance_helper.py | 46 ++++++++ nova/tests/api/openstack/test_server_actions.py | 144 ++++++++++++++++++++++++ 2 files changed, 190 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index eec861428..894d47beb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -360,6 +360,12 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, 'createBackup': self._action_create_backup, + 'changePassword': self._action_change_password, + 'reboot': self._action_reboot, + 'rebuild': self._action_rebuild, + 'resize': self._action_resize, + 'confirmResize': self._action_confirm_resize, + 'revertResize': self._action_revert_resize, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -373,6 +379,46 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): attributes = ('name', 'backup_type', 'rotation') return self._deserialize_image_action(node, attributes) + def _action_change_password(self, node): + if not node.hasAttribute("adminPass"): + raise AttributeError("No adminPass was specified in request") + return {"adminPass": node.getAttribute("adminPass")} + + def _action_reboot(self, node): + if not node.hasAttribute("type"): + raise AttributeError("No reboot type was specified in request") + return {"type": node.getAttribute("type")} + + def _action_rebuild(self, node): + rebuild = {} + if node.hasAttribute("name"): + rebuild['name'] = node.getAttribute("name") + + metadata_node = self.find_first_child_named(node, "metadata") + if metadata_node is not None: + rebuild["metadata"] = self.extract_metadata(metadata_node) + + personality = self._extract_personality(node) + if personality is not None: + rebuild["personality"] = personality + + if not node.hasAttribute("imageRef"): + raise AttributeError("No imageRef was specified in request") + rebuild["imageRef"] = node.getAttribute("imageRef") + + return rebuild + + def _action_resize(self, node): + if not node.hasAttribute("flavorRef"): + raise AttributeError("No flavorRef was specified in request") + return {"flavorRef": node.getAttribute("flavorRef")} + + def _action_confirm_resize(self, node): + return None + + def _action_revert_resize(self, node): + return None + def _deserialize_image_action(self, node, allowed_attributes): data = {} for attribute in allowed_attributes: diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index fe627987f..b175a7ff2 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -770,3 +770,147 @@ class TestServerActionXMLDeserializerV11(test.TestCase): }, } self.assertEquals(request['body'], expected) + + def test_change_pass(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "changePassword": { + "adminPass": "1234pass", + }, + } + self.assertEquals(request['body'], expected) + + def test_change_pass_no_pass(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_reboot(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "reboot": { + "type": "HARD", + }, + } + self.assertEquals(request['body'], expected) + + def test_reboot_no_type(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "resize": { + "flavorRef": "http://localhost/flavors/3" + }, + } + self.assertEquals(request['body'], expected) + + def test_resize_no_flavor_ref(self): + serial_request = """ + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') + + def test_confirm_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "confirmResize": None, + } + self.assertEquals(request['body'], expected) + + def test_revert_resize(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "revertResize": None, + } + self.assertEquals(request['body'], expected) + + def test_rebuild(self): + serial_request = """ + + + Apache1 + + + Mg== + + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "name": "new-server-test", + "imageRef": "http://localhost/images/1", + "metadata": { + "My Server Name": "Apache1", + }, + "personality": [ + {"path": "/etc/banner.txt", "contents": "Mg=="}, + ], + }, + } + self.assertDictEqual(request['body'], expected) + + def test_rebuild_minimum(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "imageRef": "http://localhost/images/1", + }, + } + self.assertDictEqual(request['body'], expected) + + def test_rebuild_no_imageRef(self): + serial_request = """ + + + Apache1 + + + Mg== + + """ + self.assertRaises(AttributeError, + self.deserializer.deserialize, + serial_request, + 'action') -- cgit From 475963efeae1b12ae7474508559fb904bc9c1675 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 4 Aug 2011 15:31:18 -0500 Subject: Added more informative docstring. --- nova/virt/xenapi/vmops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 81c6c6967..69f1af75a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -744,7 +744,10 @@ class VMOps(object): LOG.exception(exc) def _find_rescue_vbd_ref(self, vm_ref, rescue_vm_ref): - """Find and return the rescue VM's vbd_ref.""" + """Find and return the rescue VM's vbd_ref. + + We use the second VBD here because swap is first with the root file + system coming in second.""" vbd_ref = self._session.get_xenapi().VM.get_VBDs(vm_ref)[1] vdi_ref = self._session.get_xenapi().VBD.get_record(vbd_ref)["VDI"] -- cgit From 5b463f5d14c62f66250d5edc3edbd2ded704e0da Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:38:55 -0400 Subject: Added missing tests for server actions Updated reboot to verify the reboot type is HARD or SOFT Fixed case of having an empty flavorref on resize --- nova/api/openstack/servers.py | 9 +- nova/tests/api/openstack/test_server_actions.py | 107 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3acc4510e..fa4d11e24 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -268,9 +268,13 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: reboot_type = input_dict['reboot']['type'] + if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + msg = _("Argument 'type' for reboot is not HARD or SOFT") + LOG.exception(msg) + raise exc.HTTPBadRequest() else: LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPUnprocessableEntity() + raise exc.HTTPBadRequest() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver @@ -646,6 +650,9 @@ class ControllerV11(Controller): """ Resizes a given instance to the flavor size requested """ try: flavor_ref = input_dict["resize"]["flavorRef"] + if not flavor_ref: + msg = _("Resize request has invalid 'flavorRef' attribute.") + raise exc.HTTPBadRequest(explanation=msg) except (KeyError, TypeError): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index b175a7ff2..b5e4246e9 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -479,6 +479,21 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_xml(self): + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = "application/xml" + req.body = """ + """ + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_not_a_string(self): body = {'changePassword': {'adminPass': 1234}} req = webob.Request.blank('/v1.1/servers/1/action') @@ -515,6 +530,42 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_reboot_hard(self): + body = dict(reboot=dict(type="HARD")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_reboot_soft(self): + body = dict(reboot=dict(type="SOFT")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_server_reboot_incorrect_type(self): + body = dict(reboot=dict(type="NOT_A_TYPE")) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_reboot_missing_type(self): + body = dict(reboot=dict()) + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_rebuild_accepted_minimum(self): body = { "rebuild": { @@ -657,6 +708,62 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) + def test_resize_server_no_flavor(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict()) + req.body = json.dumps(body_dict) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_resize_server_no_flavor_ref(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict(flavorRef=None)) + req.body = json.dumps(body_dict) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_confirm_resize_server(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(confirmResize=None) + req.body = json.dumps(body_dict) + + self.confirm_resize_called = False + + def cr_mock(*args): + self.confirm_resize_called = True + + self.stubs.Set(nova.compute.api.API, 'confirm_resize', cr_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) + self.assertEqual(self.confirm_resize_called, True) + + def test_revert_resize_server(self): + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(revertResize=None) + req.body = json.dumps(body_dict) + + self.revert_resize_called = False + + def revert_mock(*args): + self.revert_resize_called = True + + self.stubs.Set(nova.compute.api.API, 'revert_resize', revert_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.revert_resize_called, True) + def test_create_image(self): body = { 'createImage': { -- 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 ++++++++++++++++++++----------------- nova/compute/api.py | 6 +++--- nova/compute/manager.py | 6 +++--- nova/tests/test_hosts.py | 31 +++++++++++++------------- nova/virt/driver.py | 2 +- nova/virt/fake.py | 2 +- nova/virt/hyperv.py | 2 +- nova/virt/libvirt/connection.py | 2 +- nova/virt/vmwareapi_conn.py | 2 +- nova/virt/xenapi/vmops.py | 8 +++---- nova/virt/xenapi_conn.py | 4 ++-- 11 files changed, 55 insertions(+), 53 deletions(-) (limited to 'nova') 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 diff --git a/nova/compute/api.py b/nova/compute/api.py index 3e0cd7cfa..5f85ca908 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -995,10 +995,10 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) - def set_host_powerstate(self, context, host, state): + def host_power_action(self, context, host, action): """Reboots or shuts down the host.""" - return self._call_compute_message("set_host_powerstate", context, - instance_id=None, host=host, params={"state": state}) + return self._call_compute_message("host_power_action", context, + instance_id=None, host=host, params={"action": action}) @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 220b09554..c2c12a9a2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -955,10 +955,10 @@ class ComputeManager(manager.SchedulerDependentManager): result)) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_host_powerstate(self, context, instance_id=None, host=None, - state=None): + def host_power_action(self, context, instance_id=None, host=None, + action=None): """Reboots or shuts down the host.""" - return self.driver.set_host_powerstate(host, state) + return self.driver.host_power_action(host, action) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def set_host_enabled(self, context, instance_id=None, host=None, diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index d8f90a109..cd22571e6 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,8 +48,8 @@ def stub_set_host_enabled(context, host, enabled): return status -def stub_set_host_powerstate(context, host, state): - return state +def stub_host_power_action(context, host, action): + return action class FakeRequest(object): @@ -66,8 +66,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) - self.stubs.Set(self.controller.compute_api, 'set_host_powerstate', - stub_set_host_powerstate) + self.stubs.Set(self.controller.compute_api, 'host_power_action', + stub_host_power_action) def test_list_hosts(self): """Verify that the compute hosts are returned.""" @@ -93,19 +93,18 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") - def test_host_power_state(self): - en_body = {"power_state": "reboot"} - result_c1 = self.controller.update(self.req, "host_c1", body=en_body) - self.assertEqual(result_c1["power_state"], "reboot") - # Test invalid power_state - en_body = {"power_state": "invalid"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=en_body) + def test_host_startup(self): + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.startup, + self.req, "host_c1") - def test_bad_power_state_value(self): - bad_body = {"power_state": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + def test_host_shutdown(self): + result = self.controller.shutdown(self.req, "host_c1") + print "RES", result + self.assertEqual(result["power_action"], "shutdown") + + def test_host_reboot(self): + result = self.controller.reboot(self.req, "host_c1") + self.assertEqual(result["power_action"], "reboot") def test_bad_status_value(self): bad_body = {"status": "bad"} diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 30f14459a..052c6607e 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -282,7 +282,7 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 7b13f4fbe..db51c258b 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -512,7 +512,7 @@ class FakeConnection(driver.ComputeDriver): """Return fake Host Status of ram, disk, network.""" return self.host_status - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 00c4d360b..f0efeb581 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -499,7 +499,7 @@ class HyperVConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index f0b2014ac..14e02c7c4 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1558,7 +1558,7 @@ class LibvirtConnection(driver.ComputeDriver): """See xenapi_conn.py implementation.""" pass - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 90d083f10..5937d9585 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -191,7 +191,7 @@ class VMWareESXConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" pass diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index dfd85dd2c..509abd767 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1024,13 +1024,13 @@ class VMOps(object): # TODO: implement this! return 'http://fakeajaxconsole/fake_url' - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" - args = {"state": json.dumps(state)} + args = {"action": json.dumps(action)} methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} - json_resp = self._call_xenhost(methods[state], args) + json_resp = self._call_xenhost(methods[action], args) resp = json.loads(json_resp) - return resp["powerstate"] + return resp["power_action"] def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index fa5b78a2a..3452343c6 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -332,9 +332,9 @@ class XenAPIConnection(driver.ComputeDriver): True, run the update first.""" return self.HostState.get_host_stats(refresh=refresh) - def set_host_powerstate(self, host, state): + def host_power_action(self, host, action): """Reboots or shuts down the host.""" - return self._vmops.set_host_powerstate(host, state) + return self._vmops.host_power_action(host, action) def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" -- 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') 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 dae356ad06bb4926ca91fd9a5182271d93c4b7be Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 21:18:55 +0000 Subject: Forgot the instance_id parameter in the finish call --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94bac9be4..a062cbe91 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -746,7 +746,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': {'migration_id': migration_ref['id']}, + 'args': { 'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) -- cgit From 77a1a63c30a9797f2f729c7b815d6660bed880d5 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 21:26:21 +0000 Subject: Bad method call --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 91d9d8530..166141e5e 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -191,7 +191,7 @@ class XenAPIConnection(driver.ComputeDriver): def revert_migration(self, instance): """Reverts a resize, powering back on the instance""" - self._vmops.revert_resize(instance) + self._vmops.revert_migration(instance) def finish_migration(self, context, instance, disk_info, network_info, resize_instance=False): -- cgit From 5fe92058d0ee11a7e9ea1c8f56b7e9350cf703e4 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 4 Aug 2011 22:18:52 +0000 Subject: Revert migration now finishes --- nova/compute/manager.py | 4 ++-- nova/tests/test_xenapi.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a062cbe91..0fd9c3c13 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -746,8 +746,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': { 'instance_id': instance_ref['uuid'], - 'migration_id': migration_ref['id']}, + 'args': {'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 5b29cf537..d269408b9 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,6 +767,53 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') + + def test_revert_migrate(self): + instance = db.instance_create(self.context, self.values) + self.called = False + self.fake_vm_start_called = False + self.fake_revert_migration_called = False + + def fake_vm_start(*args, **kwargs): + self.fake_vm_start_called = True + + def fake_vdi_resize(*args, **kwargs): + self.called = True + + def fake_revert_migration(*args, **kwargs): + self.fake_revert_migration_called = True + + self.stubs.Set(stubs.FakeSessionForMigrationTests, + "VDI_resize_online", fake_vdi_resize) + self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) + self.stubs.Set(vmops.VMOps, 'revert_migration', fake_revert_migration) + + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + stubs.stubout_loopingcall_start(self.stubs) + conn = xenapi_conn.get_connection(False) + network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, + {'broadcast': '192.168.0.255', + 'dns': ['192.168.0.1'], + 'gateway': '192.168.0.1', + 'gateway6': 'dead:beef::1', + 'ip6s': [{'enabled': '1', + 'ip': 'dead:beef::dcad:beff:feef:0', + 'netmask': '64'}], + 'ips': [{'enabled': '1', + 'ip': '192.168.0.100', + 'netmask': '255.255.255.0'}], + 'label': 'fake', + 'mac': 'DE:AD:BE:EF:00:00', + 'rxtx_cap': 3})] + conn.finish_migration(self.context, instance, + dict(base_copy='hurr', cow='durr'), + network_info, resize_instance=True) + self.assertEqual(self.called, True) + self.assertEqual(self.fake_vm_start_called, True) + + conn.revert_migration(instance) + self.assertEqual(self.fake_revert_migration_called, True) + def test_finish_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- cgit From d470f82ea09214b6f535995aa551a23eb4ccc4ed Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Thu, 4 Aug 2011 17:43:37 -0700 Subject: Properly format mapping['dns'] before handing off to template for injection (Fixes LP Bug #821203) --- nova/virt/libvirt/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index f7aae4112..0f0f62588 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -880,7 +880,7 @@ class LibvirtConnection(driver.ComputeDriver): 'netmask': netmask, 'gateway': mapping['gateway'], 'broadcast': mapping['broadcast'], - 'dns': mapping['dns'], + 'dns': ' '.join(mapping['dns']), 'address_v6': address_v6, 'gateway6': gateway_v6, 'netmask_v6': netmask_v6} -- cgit From 637dfc0f44cbd5bf0c76d80d708a241e562403ac Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 01:55:53 +0000 Subject: Added explanations to exceptions and cleaned up reboot types --- nova/api/openstack/servers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fa4d11e24..9a55af8ea 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -267,14 +267,16 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: - reboot_type = input_dict['reboot']['type'] - if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + valid_reboot_types = ['HARD', 'SOFT'] + reboot_type = input_dict['reboot']['type'].upper() + if not valid_reboot_types.count(reboot_type): msg = _("Argument 'type' for reboot is not HARD or SOFT") LOG.exception(msg) - raise exc.HTTPBadRequest() + raise exc.HTTPBadRequest(explanation=msg) else: - LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPBadRequest() + msg = _("Missing argument 'type' for reboot") + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver -- cgit From b068e1edd79a5382ef1237d0b47e60dc141bae35 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 5 Aug 2011 07:55:22 +0400 Subject: added missing tests --- nova/tests/test_nova_manage.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nova/tests/test_nova_manage.py (limited to 'nova') diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py new file mode 100644 index 000000000..ca9ffa222 --- /dev/null +++ b/nova/tests/test_nova_manage.py @@ -0,0 +1,79 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# Copyright 2011 Ilya Alekseyev +# +# 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 + +TOPDIR = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir)) +NOVA_MANAGE_PATH = os.path.join(TOPDIR, 'bin', 'nova-manage') + +import imp +nova_manage = imp.load_source('nova_manage', NOVA_MANAGE_PATH) + +import netaddr +from nova import context +from nova import db +from nova import flags +from nova import test + +FLAGS = flags.FLAGS + + +class FixedIpCommandsTestCase(test.TestCase): + def setUp(self): + super(FixedIpCommandsTestCase, self).setUp() + cidr = '10.0.0.0/24' + net = netaddr.IPNetwork(cidr) + net_info = {'bridge': 'fakebr', + 'bridge_interface': 'fakeeth', + 'dns': FLAGS.flat_network_dns, + 'cidr': cidr, + 'netmask': str(net.netmask), + 'gateway': str(net[1]), + 'broadcast': str(net.broadcast), + 'dhcp_start': str(net[2])} + self.network = db.network_create_safe(context.get_admin_context(), + net_info) + num_ips = len(net) + for index in range(num_ips): + address = str(net[index]) + reserved = (index == 1 or index == 2) + db.fixed_ip_create(context.get_admin_context(), + {'network_id': self.network['id'], + 'address': address, + 'reserved': reserved}) + self.commands = nova_manage.FixedIpCommands() + + def tearDown(self): + db.network_delete_safe(context.get_admin_context(), self.network['id']) + super(FixedIpCommandsTestCase, self).tearDown() + + def test_reserve(self): + self.commands.reserve('10.0.0.100') + address = db.fixed_ip_get_by_address(context.get_admin_context(), + '10.0.0.100') + self.assertEqual(address['reserved'], True) + + def test_waste(self): + db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', + {'reserved': True}) + self.commands.waste('10.0.0.100') + address = db.fixed_ip_get_by_address(context.get_admin_context(), + '10.0.0.100') + self.assertEqual(address['reserved'], False) -- cgit From 625330bbc70b7be0d007c2a5ce1fba4dfcc29bf8 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 22:59:36 -0500 Subject: utilized functools.wraps --- nova/test.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/test.py b/nova/test.py index 97cd3e0d0..d3b9d3ee2 100644 --- a/nova/test.py +++ b/nova/test.py @@ -60,11 +60,10 @@ class skip_test(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" raise nose.SkipTest(self.message) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper @@ -75,13 +74,12 @@ class skip_if(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" if self.condition: raise nose.SkipTest(self.message) func(*args, **kw) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper @@ -92,13 +90,12 @@ class skip_unless(object): self.message = msg def __call__(self, func): + @functools.wraps(func) def _skipper(*args, **kw): """Wrapped skipper function.""" if not self.condition: raise nose.SkipTest(self.message) func(*args, **kw) - _skipper.__name__ = func.__name__ - _skipper.__doc__ = func.__doc__ return _skipper -- cgit From 13fb04effa25e860eaa9e4ebaca2b4d906a140b0 Mon Sep 17 00:00:00 2001 From: Justin Shepherd Date: Thu, 4 Aug 2011 23:01:22 -0500 Subject: fixed pep8 issue --- nova/tests/test_skip_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_skip_examples.py b/nova/tests/test_skip_examples.py index 033f1ea2e..8ca203442 100644 --- a/nova/tests/test_skip_examples.py +++ b/nova/tests/test_skip_examples.py @@ -37,7 +37,7 @@ class ExampleSkipTestCase(test.TestCase): @test.skip_if(False, "This test case should never be skipped.") def test_001_increase_test_counter(self): ExampleSkipTestCase.test_counter += 1 - + @test.skip_unless(True, "This test case should never be skipped.") def test_002_increase_test_counter(self): ExampleSkipTestCase.test_counter += 1 -- cgit From 2a89883297f6b5398abb7486e9e26c12ab0fc0ec Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 5 Aug 2011 14:02:55 +0200 Subject: Remove spurious direct use of subprocess --- nova/console/xvp.py | 1 - nova/utils.py | 4 ++++ nova/virt/libvirt/connection.py | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 3cd287183..2d6842044 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -20,7 +20,6 @@ import fcntl import os import signal -import subprocess from Cheetah import Template diff --git a/nova/utils.py b/nova/utils.py index 1e2dbebb1..3a2049464 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -137,6 +137,8 @@ def execute(*cmd, **kwargs): :delay_on_retry True | False. Defaults to True. If set to True, wait a short amount of time before retrying. :attempts How many times to retry cmd. + :shell True | False. Defaults to False. If set to True, + Popen command is called shell=True. :raises exception.Error on receiving unknown arguments :raises exception.ProcessExecutionError @@ -147,6 +149,7 @@ def execute(*cmd, **kwargs): check_exit_code = kwargs.pop('check_exit_code', 0) delay_on_retry = kwargs.pop('delay_on_retry', True) attempts = kwargs.pop('attempts', 1) + shell = kwargs.pop('shell', False) if len(kwargs): raise exception.Error(_('Got unknown keyword args ' 'to utils.execute: %r') % kwargs) @@ -164,6 +167,7 @@ def execute(*cmd, **kwargs): stdin=_PIPE, stdout=_PIPE, stderr=_PIPE, + shell=shell, env=env) result = None if process_input is not None: diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0b93a8399..ec202a4ef 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -43,7 +43,6 @@ import os import random import re import shutil -import subprocess import sys import tempfile import time @@ -684,7 +683,7 @@ class LibvirtConnection(driver.ComputeDriver): cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ % (utils.novadir(), ajaxterm_cmd, token, port) - subprocess.Popen(cmd, shell=True) + utils.execute(cmd, shell=True) return {'token': token, 'host': host, 'port': port} def get_host_ip_addr(self): -- cgit From ecdb1c87fb3904080d48f3fd3b3619f6c4a40df9 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 5 Aug 2011 14:33:12 +0200 Subject: Add run_as_root parameter to utils.execute, uses new sudo_helper FLAG to prefix command --- nova/flags.py | 3 +++ nova/utils.py | 8 ++++++++ 2 files changed, 11 insertions(+) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 12c6d1356..e30c0d6be 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -392,3 +392,6 @@ DEFINE_bool('start_guests_on_host_boot', False, 'Whether to restart guests when the host reboots') DEFINE_bool('resume_guests_state_on_host_boot', False, 'Whether to start guests, that was running before the host reboot') + +DEFINE_string('sudo_helper', 'sudo', + 'Command prefix to use for running commands as root') diff --git a/nova/utils.py b/nova/utils.py index 3a2049464..0820a2bde 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -28,6 +28,7 @@ import netaddr import os import random import re +import shlex import socket import struct import sys @@ -139,6 +140,9 @@ def execute(*cmd, **kwargs): :attempts How many times to retry cmd. :shell True | False. Defaults to False. If set to True, Popen command is called shell=True. + :run_as_root True | False. Defaults to False. If set to True, + the command is prefixed by the command specified + in the sudo_helper FLAG. :raises exception.Error on receiving unknown arguments :raises exception.ProcessExecutionError @@ -150,9 +154,13 @@ def execute(*cmd, **kwargs): delay_on_retry = kwargs.pop('delay_on_retry', True) attempts = kwargs.pop('attempts', 1) shell = kwargs.pop('shell', False) + run_as_root = kwargs.pop('run_as_root', False) if len(kwargs): raise exception.Error(_('Got unknown keyword args ' 'to utils.execute: %r') % kwargs) + + if run_as_root: + cmd = shlex.split(FLAGS.sudo_helper) + cmd cmd = map(str, cmd) while attempts > 0: -- cgit From 9742ff588d3ce90b1420eaf95e311f02a4e196da Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 14:54:56 +0200 Subject: Add generic image service tests. --- nova/image/fake.py | 41 ++++++++++---- nova/tests/test_image.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 nova/tests/test_image.py (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index 28e912534..b29ec6517 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -45,9 +45,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': False, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel, 'architecture': 'x86_64'}} @@ -56,9 +59,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -66,9 +72,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -76,9 +85,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -86,9 +98,12 @@ class _FakeImageService(service.BaseImageService): 'name': 'fakeimage123456', 'created_at': timestamp, 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, 'status': 'active', - 'container_format': 'ami', - 'disk_format': 'raw', + 'is_public': True, +# 'container_format': 'ami', +# 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, 'ramdisk_id': FLAGS.null_kernel}} @@ -101,7 +116,11 @@ class _FakeImageService(service.BaseImageService): def index(self, context, filters=None, marker=None, limit=None): """Returns list of images.""" - return copy.deepcopy(self.images.values()) + retval = [] + for img in self.images.values(): + retval += [dict([(k, v) for k, v in img.iteritems() + if k in ['id', 'name']])] + return retval def detail(self, context, filters=None, marker=None, limit=None): """Return list of detailed image information.""" diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py new file mode 100644 index 000000000..ba5b93f9b --- /dev/null +++ b/nova/tests/test_image.py @@ -0,0 +1,135 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Linux2Go +# Author: Soren Hansen +# +# 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 datetime + +from nova import context +from nova import exception +from nova import test +import nova.image + +class _ImageTestCase(test.TestCase): + def setUp(self): + super(_ImageTestCase, self).setUp() + self.context = context.get_admin_context() + + def test_index(self): + res = self.image_service.index(self.context) + for image in res: + self.assertEquals(set(image.keys()), set(['id', 'name'])) + + def test_detail(self): + res = self.image_service.detail(self.context) + for image in res: + keys = set(image.keys()) + self.assertEquals(keys, set(['id', 'name', 'created_at', + 'updated_at', 'deleted_at', 'deleted', + 'status', 'is_public', 'properties'])) + self.assertTrue(isinstance(image['created_at'], datetime.datetime)) + self.assertTrue(isinstance(image['updated_at'], datetime.datetime)) + + if not (isinstance(image['deleted_at'], datetime.datetime) or + image['deleted_at'] is None): + self.fail('image\'s "deleted_at" attribute was neither a ' + 'datetime object nor None') + + def check_is_bool(image, key): + val = image.get('deleted') + if not isinstance(val, bool): + self.fail('image\'s "%s" attribute wasn\'t ' + 'a bool: %r' % (key, val)) + + check_is_bool(image, 'deleted') + check_is_bool(image, 'is_public') + + def test_index_and_detail_have_same_results(self): + index = self.image_service.index(self.context) + detail = self.image_service.detail(self.context) + index_set = set([(i['id'], i['name']) for i in index]) + detail_set = set([(i['id'], i['name']) for i in detail]) + self.assertEqual(index_set, detail_set) + + def test_show_raises_imagenotfound_for_invalid_id(self): + self.assertRaises(exception.ImageNotFound, + self.image_service.show, + self.context, + 'this image does not exist') + + def test_show_by_name(self): + self.assertRaises(exception.ImageNotFound, + self.image_service.show_by_name, + self.context, + 'this image does not exist') + + def test_create_adds_id(self): + index = self.image_service.index(self.context) + image_count = len(index) + + self.image_service.create(self.context, {}) + + index = self.image_service.index(self.context) + self.assertEquals(len(index), image_count+1) + + self.assertTrue(index[0]['id']) + + + def test_create_keeps_id(self): + self.image_service.create(self.context, {'id': '34'}) + self.image_service.show(self.context, '34') + + + def test_create_rejects_duplicate_ids(self): + self.image_service.create(self.context, {'id': '34'}) + self.assertRaises(exception.Duplicate, + self.image_service.create, + self.context, + {'id': '34'}) + + # Make sure there's still one left + self.image_service.show(self.context, '34') + + def test_update(self): + self.image_service.create(self.context, + {'id': '34', 'foo': 'bar'}) + + self.image_service.update(self.context, '34', + {'id': '34', 'foo': 'baz'}) + + img = self.image_service.show(self.context, '34') + self.assertEquals(img['foo'], 'baz') + + def test_delete(self): + self.image_service.create(self.context, {'id': '34', 'foo': 'bar'}) + self.image_service.delete(self.context, '34') + self.assertRaises(exception.NotFound, + self.image_service.show, + self.context, + '34') + + def test_delete_all(self): + self.image_service.create(self.context, {'id': '32', 'foo': 'bar'}) + self.image_service.create(self.context, {'id': '33', 'foo': 'bar'}) + self.image_service.create(self.context, {'id': '34', 'foo': 'bar'}) + self.image_service.delete_all() + index = self.image_service.index(self.context) + self.assertEquals(len(index), 0) + +class FakeImageTestCase(_ImageTestCase): + def setUp(self): + super(FakeImageTestCase, self).setUp() + self.image_service = nova.image.fake.FakeImageService() + -- 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') 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 1e366d9de180347b47675bf7f811ffd5a293ef10 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 15:37:36 +0200 Subject: pep8 --- nova/image/fake.py | 2 +- nova/tests/test_image.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index b29ec6517..97af81711 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -118,7 +118,7 @@ class _FakeImageService(service.BaseImageService): """Returns list of images.""" retval = [] for img in self.images.values(): - retval += [dict([(k, v) for k, v in img.iteritems() + retval += [dict([(k, v) for k, v in img.iteritems() if k in ['id', 'name']])] return retval diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py index ba5b93f9b..5ec8812f3 100644 --- a/nova/tests/test_image.py +++ b/nova/tests/test_image.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 Linux2Go +# Copyright 2011 OpenStack LLC # Author: Soren Hansen # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -82,16 +82,14 @@ class _ImageTestCase(test.TestCase): self.image_service.create(self.context, {}) index = self.image_service.index(self.context) - self.assertEquals(len(index), image_count+1) + self.assertEquals(len(index), image_count + 1) self.assertTrue(index[0]['id']) - def test_create_keeps_id(self): self.image_service.create(self.context, {'id': '34'}) self.image_service.show(self.context, '34') - def test_create_rejects_duplicate_ids(self): self.image_service.create(self.context, {'id': '34'}) self.assertRaises(exception.Duplicate, @@ -132,4 +130,3 @@ class FakeImageTestCase(_ImageTestCase): def setUp(self): super(FakeImageTestCase, self).setUp() self.image_service = nova.image.fake.FakeImageService() - -- cgit From 070d832dad062dfb18439e77b4460e6ebe75bdb0 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 5 Aug 2011 15:38:25 +0200 Subject: Make disk_format and container_format optional for libvirt's snapshot implementation. --- nova/virt/libvirt/connection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 0b93a8399..3663337e9 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -392,9 +392,7 @@ class LibvirtConnection(driver.ComputeDriver): nova.image.get_image_service(image_href) snapshot = snapshot_image_service.show(context, snapshot_image_id) - metadata = {'disk_format': base['disk_format'], - 'container_format': base['container_format'], - 'is_public': False, + metadata = {'is_public': False, 'status': 'active', 'name': snapshot['name'], 'properties': { @@ -409,6 +407,12 @@ class LibvirtConnection(driver.ComputeDriver): arch = base['properties']['architecture'] metadata['properties']['architecture'] = arch + if 'disk_format' in base: + metadata['disk_format'] = base['disk_format'] + + if 'container_format' in base: + metadata['container_format'] = base['container_format'] + # Make the snapshot snapshot_name = uuid.uuid4().hex snapshot_xml = """ -- 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') 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 19e50320e36f02ce11a6aaae8f88a6ddbc132859 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 5 Aug 2011 07:21:55 -0700 Subject: pep8 violations sneaking into trunk? --- nova/db/sqlalchemy/api.py | 3 ++- nova/tests/test_xenapi.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ce12ba4e0..8cfa94ed7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3301,7 +3301,8 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): +def instance_type_extra_specs_get_item(context, instance_type_id, key, + session=None): if not session: session = get_session() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8048e5341..dfc1eeb0a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,7 +767,6 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_revert_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- cgit From 38756955417e5c2fad7c8848252c5a2334912e02 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 5 Aug 2011 16:23:48 +0200 Subject: Removed most direct sudo calls, make them use run_as_root=True instead --- nova/network/linux_net.py | 78 ++++++++++++++++-------------- nova/tests/test_libvirt.py | 10 ++-- nova/tests/test_volume.py | 10 ++-- nova/tests/test_xenapi.py | 10 ++-- nova/virt/disk.py | 47 +++++++++--------- nova/virt/libvirt/connection.py | 7 +-- nova/virt/libvirt/vif.py | 16 ++++--- nova/virt/xenapi/vm_utils.py | 15 +++--- nova/virt/xenapi/volume_utils.py | 4 +- nova/volume/driver.py | 101 +++++++++++++++++++++------------------ 10 files changed, 161 insertions(+), 137 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 8ace07884..3c62c75cd 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -296,14 +296,14 @@ class IptablesManager(object): for cmd, tables in s: for table in tables: - current_table, _ = self.execute('sudo', - '%s-save' % (cmd,), + current_table, _ = self.execute('%s-save' % (cmd,), '-t', '%s' % (table,), + run_as_root=True, attempts=5) current_lines = current_table.split('\n') new_filter = self._modify_rules(current_lines, tables[table]) - self.execute('sudo', '%s-restore' % (cmd,), + self.execute('%s-restore' % (cmd,), run_as_root=True, process_input='\n'.join(new_filter), attempts=5) @@ -396,21 +396,22 @@ def init_host(): def bind_floating_ip(floating_ip, check_exit_code=True): """Bind ip to public interface.""" - _execute('sudo', 'ip', 'addr', 'add', floating_ip, + _execute('ip', 'addr', 'add', floating_ip, 'dev', FLAGS.public_interface, - check_exit_code=check_exit_code) + run_as_root=True, check_exit_code=check_exit_code) def unbind_floating_ip(floating_ip): """Unbind a public ip from public interface.""" - _execute('sudo', 'ip', 'addr', 'del', floating_ip, - 'dev', FLAGS.public_interface) + _execute('ip', 'addr', 'del', floating_ip, + 'dev', FLAGS.public_interface, run_as_root=True) def ensure_metadata_ip(): """Sets up local metadata ip.""" - _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', - 'scope', 'link', 'dev', 'lo', check_exit_code=False) + _execute('ip', 'addr', 'add', '169.254.169.254/32', + 'scope', 'link', 'dev', 'lo', + run_as_root=True, check_exit_code=False) def ensure_vlan_forward(public_ip, port, private_ip): @@ -464,9 +465,11 @@ def ensure_vlan(vlan_num, bridge_interface): interface = 'vlan%s' % vlan_num if not _device_exists(interface): LOG.debug(_('Starting VLAN inteface %s'), interface) - _execute('sudo', 'vconfig', 'set_name_type', 'VLAN_PLUS_VID_NO_PAD') - _execute('sudo', 'vconfig', 'add', bridge_interface, vlan_num) - _execute('sudo', 'ip', 'link', 'set', interface, 'up') + _execute('vconfig', 'set_name_type', 'VLAN_PLUS_VID_NO_PAD', + run_as_root=True) + _execute('vconfig', 'add', bridge_interface, vlan_num, + run_as_root=True) + _execute('ip', 'link', 'set', interface, 'up', run_as_root=True) return interface @@ -487,58 +490,63 @@ def ensure_bridge(bridge, interface, net_attrs=None): """ if not _device_exists(bridge): LOG.debug(_('Starting Bridge interface for %s'), interface) - _execute('sudo', 'brctl', 'addbr', bridge) - _execute('sudo', 'brctl', 'setfd', bridge, 0) + _execute('brctl', 'addbr', bridge, run_as_root=True) + _execute('brctl', 'setfd', bridge, 0, run_as_root=True) # _execute('sudo brctl setageing %s 10' % bridge) - _execute('sudo', 'brctl', 'stp', bridge, 'off') - _execute('sudo', 'ip', 'link', 'set', bridge, 'up') + _execute('brctl', 'stp', bridge, 'off', run_as_root=True) + _execute('ip', 'link', 'set', bridge, 'up', run_as_root=True) if net_attrs: # NOTE(vish): The ip for dnsmasq has to be the first address on the # bridge for it to respond to reqests properly suffix = net_attrs['cidr'].rpartition('/')[2] - out, err = _execute('sudo', 'ip', 'addr', 'add', + out, err = _execute('ip', 'addr', 'add', '%s/%s' % (net_attrs['dhcp_server'], suffix), 'brd', net_attrs['broadcast'], 'dev', bridge, + run_as_root=True, check_exit_code=False) if err and err != 'RTNETLINK answers: File exists\n': raise exception.Error('Failed to add ip: %s' % err) if(FLAGS.use_ipv6): - _execute('sudo', 'ip', '-f', 'inet6', 'addr', + _execute('ip', '-f', 'inet6', 'addr', 'change', net_attrs['cidr_v6'], - 'dev', bridge) + 'dev', bridge, run_as_root=True) # NOTE(vish): If the public interface is the same as the # bridge, then the bridge has to be in promiscuous # to forward packets properly. if(FLAGS.public_interface == bridge): - _execute('sudo', 'ip', 'link', 'set', - 'dev', bridge, 'promisc', 'on') + _execute('ip', 'link', 'set', + 'dev', bridge, 'promisc', 'on', run_as_root=True) if interface: # NOTE(vish): This will break if there is already an ip on the # interface, so we move any ips to the bridge gateway = None - out, err = _execute('sudo', 'route', '-n') + out, err = _execute('route', '-n', run_as_root=True) for line in out.split('\n'): fields = line.split() if fields and fields[0] == '0.0.0.0' and fields[-1] == interface: gateway = fields[1] - _execute('sudo', 'route', 'del', 'default', 'gw', gateway, - 'dev', interface, check_exit_code=False) - out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, - 'scope', 'global') + _execute('route', 'del', 'default', 'gw', gateway, + 'dev', interface, + check_exit_code=False, run_as_root=True) + out, err = _execute('ip', 'addr', 'show', 'dev', interface, + 'scope', 'global', run_as_root=True) for line in out.split('\n'): fields = line.split() if fields and fields[0] == 'inet': params = fields[1:-1] - _execute(*_ip_bridge_cmd('del', params, fields[-1])) - _execute(*_ip_bridge_cmd('add', params, bridge)) + _execute(*_ip_bridge_cmd('del', params, fields[-1]), + run_as_root=True) + _execute(*_ip_bridge_cmd('add', params, bridge), + run_as_root=True) if gateway: - _execute('sudo', 'route', 'add', 'default', 'gw', gateway) - out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, - check_exit_code=False) + _execute('route', 'add', 'default', 'gw', gateway, + run_as_root=True) + out, err = _execute('brctl', 'addif', bridge, interface, + check_exit_code=False, run_as_root=True) if (err and err != "device %s is already a member of a bridge; can't " "enslave it to bridge %s.\n" % (interface, bridge)): @@ -602,7 +610,7 @@ def update_dhcp(context, network_ref): check_exit_code=False) if conffile in out: try: - _execute('sudo', 'kill', '-HUP', pid) + _execute('kill', '-HUP', pid, run_as_root=True) return except Exception as exc: # pylint: disable=W0703 LOG.debug(_('Hupping dnsmasq threw %s'), exc) @@ -646,7 +654,7 @@ interface %s % pid, check_exit_code=False) if conffile in out: try: - _execute('sudo', 'kill', pid) + _execute('kill', pid, run_as_root=True) except Exception as exc: # pylint: disable=W0703 LOG.debug(_('killing radvd threw %s'), exc) else: @@ -732,7 +740,7 @@ def _stop_dnsmasq(network): if pid: try: - _execute('sudo', 'kill', '-TERM', pid) + _execute('kill', '-TERM', pid, run_as_root=True) except Exception as exc: # pylint: disable=W0703 LOG.debug(_('Killing dnsmasq threw %s'), exc) @@ -788,7 +796,7 @@ def _ra_pid_for(bridge): def _ip_bridge_cmd(action, params, device): """Build commands to add/del ips to bridges/devices.""" - cmd = ['sudo', 'ip', 'addr', action] + cmd = ['ip', 'addr', action] cmd.extend(params) cmd.extend(['dev', device]) return cmd diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index f8b866985..1baf373bf 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -884,18 +884,18 @@ class IptablesFirewallTestCase(test.TestCase): # self.fw.add_instance(instance_ref) def fake_iptables_execute(*cmd, **kwargs): process_input = kwargs.get('process_input', None) - if cmd == ('sudo', 'ip6tables-save', '-t', 'filter'): + if cmd == ('ip6tables-save', '-t', 'filter'): return '\n'.join(self.in6_filter_rules), None - if cmd == ('sudo', 'iptables-save', '-t', 'filter'): + if cmd == ('iptables-save', '-t', 'filter'): return '\n'.join(self.in_filter_rules), None - if cmd == ('sudo', 'iptables-save', '-t', 'nat'): + if cmd == ('iptables-save', '-t', 'nat'): return '\n'.join(self.in_nat_rules), None - if cmd == ('sudo', 'iptables-restore'): + if cmd == ('iptables-restore',): lines = process_input.split('\n') if '*filter' in lines: self.out_rules = lines return '', '' - if cmd == ('sudo', 'ip6tables-restore'): + if cmd == ('ip6tables-restore',): lines = process_input.split('\n') if '*filter' in lines: self.out6_rules = lines diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index c0f89601f..7888b6b0b 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -414,8 +414,9 @@ class ISCSITestCase(DriverTestCase): self.mox.StubOutWithMock(self.volume.driver, '_execute') for i in volume_id_list: tid = db.volume_get_iscsi_target_num(self.context, i) - self.volume.driver._execute("sudo", "ietadm", "--op", "show", - "--tid=%(tid)d" % locals()) + self.volume.driver._execute("ietadm", "--op", "show", + "--tid=%(tid)d" % locals(), + run_as_root=True) self.stream.truncate(0) self.mox.ReplayAll() @@ -433,8 +434,9 @@ class ISCSITestCase(DriverTestCase): # the first vblade process isn't running tid = db.volume_get_iscsi_target_num(self.context, volume_id_list[0]) self.mox.StubOutWithMock(self.volume.driver, '_execute') - self.volume.driver._execute("sudo", "ietadm", "--op", "show", - "--tid=%(tid)d" % locals()).AndRaise( + self.volume.driver._execute("ietadm", "--op", "show", + "--tid=%(tid)d" % locals(), + run_as_root=True).AndRaise( exception.ProcessExecutionError()) self.mox.ReplayAll() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 39ab23d9b..84480d26f 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -548,8 +548,8 @@ class XenAPIVMTestCase(test.TestCase): return '', '' fake_utils.fake_execute_set_repliers([ - # Capture the sudo tee .../etc/network/interfaces command - (r'(sudo\s+)?tee.*interfaces', _tee_handler), + # Capture the tee .../etc/network/interfaces command + (r'tee.*interfaces', _tee_handler), ]) self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, @@ -592,9 +592,9 @@ class XenAPIVMTestCase(test.TestCase): return '', '' fake_utils.fake_execute_set_repliers([ - (r'(sudo\s+)?mount', _mount_handler), - (r'(sudo\s+)?umount', _umount_handler), - (r'(sudo\s+)?tee.*interfaces', _tee_handler)]) + (r'mount', _mount_handler), + (r'umount', _umount_handler), + (r'tee.*interfaces', _tee_handler)]) self._test_spawn(1, 2, 3, check_injection=True) # tee must not run in this case, where an injection-capable diff --git a/nova/virt/disk.py b/nova/virt/disk.py index f8aea1f34..19f3ec185 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -73,7 +73,7 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): try: if not partition is None: # create partition - out, err = utils.execute('sudo', 'kpartx', '-a', device) + out, err = utils.execute('kpartx', '-a', device, run_as_root=True) if err: raise exception.Error(_('Failed to load partition: %s') % err) mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], @@ -90,14 +90,14 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): mapped_device) # Configure ext2fs so that it doesn't auto-check every N boots - out, err = utils.execute('sudo', 'tune2fs', - '-c', 0, '-i', 0, mapped_device) + out, err = utils.execute('tune2fs', '-c', 0, '-i', 0, + mapped_device, run_as_root=True) tmpdir = tempfile.mkdtemp() try: # mount loopback to dir - out, err = utils.execute( - 'sudo', 'mount', mapped_device, tmpdir) + out, err = utils.execute('mount', mapped_device, tmpdir, + run_as_root=True) if err: raise exception.Error(_('Failed to mount filesystem: %s') % err) @@ -106,14 +106,14 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): inject_data_into_fs(tmpdir, key, net, utils.execute) finally: # unmount device - utils.execute('sudo', 'umount', mapped_device) + utils.execute('umount', mapped_device, run_as_root=True) finally: # remove temporary directory utils.execute('rmdir', tmpdir) finally: if not partition is None: # remove partitions - utils.execute('sudo', 'kpartx', '-d', device) + utils.execute('kpartx', '-d', device, run_as_root=True) finally: _unlink_device(device, nbd) @@ -128,7 +128,7 @@ def setup_container(image, container_dir=None, nbd=False): """ try: device = _link_device(image, nbd) - utils.execute('sudo', 'mount', device, container_dir) + utils.execute('mount', device, container_dir, run_as_root=True) except Exception, exn: LOG.exception(_('Failed to mount filesystem: %s'), exn) _unlink_device(device, nbd) @@ -144,9 +144,9 @@ def destroy_container(target, instance, nbd=False): """ try: container_dir = '%s/rootfs' % target - utils.execute('sudo', 'umount', container_dir) + utils.execute('umount', container_dir, run_as_root=True) finally: - out, err = utils.execute('sudo', 'losetup', '-a') + out, err = utils.execute('losetup', '-a', run_as_root=True) for loop in out.splitlines(): if instance['name'] in loop: device = loop.split(loop, ':') @@ -157,7 +157,7 @@ def _link_device(image, nbd): """Link image to device using loopback or nbd""" if nbd: device = _allocate_device() - utils.execute('sudo', 'qemu-nbd', '-c', device, image) + utils.execute('qemu-nbd', '-c', device, image, run_as_root=True) # NOTE(vish): this forks into another process, so give it a chance # to set up before continuuing for i in xrange(FLAGS.timeout_nbd): @@ -166,7 +166,8 @@ def _link_device(image, nbd): time.sleep(1) raise exception.Error(_('nbd device %s did not show up') % device) else: - out, err = utils.execute('sudo', 'losetup', '--find', '--show', image) + out, err = utils.execute('losetup', '--find', '--show', image, + run_as_root=True) if err: raise exception.Error(_('Could not attach image to loopback: %s') % err) @@ -176,10 +177,10 @@ def _link_device(image, nbd): def _unlink_device(device, nbd): """Unlink image from device using loopback or nbd""" if nbd: - utils.execute('sudo', 'qemu-nbd', '-d', device) + utils.execute('qemu-nbd', '-d', device, run_as_root=True) _free_device(device) else: - utils.execute('sudo', 'losetup', '--detach', device) + utils.execute('losetup', '--detach', device, run_as_root=True) _DEVICES = ['/dev/nbd%s' % i for i in xrange(FLAGS.max_nbd_devices)] @@ -220,12 +221,12 @@ def _inject_key_into_fs(key, fs, execute=None): fs is the path to the base of the filesystem into which to inject the key. """ sshdir = os.path.join(fs, 'root', '.ssh') - utils.execute('sudo', 'mkdir', '-p', sshdir) # existing dir doesn't matter - utils.execute('sudo', 'chown', 'root', sshdir) - utils.execute('sudo', 'chmod', '700', sshdir) + utils.execute('mkdir', '-p', sshdir, run_as_root=True) + utils.execute('chown', 'root', sshdir, run_as_root=True) + utils.execute('chmod', '700', sshdir, run_as_root=True) keyfile = os.path.join(sshdir, 'authorized_keys') - utils.execute('sudo', 'tee', '-a', keyfile, - process_input='\n' + key.strip() + '\n') + utils.execute('tee', '-a', keyfile, + process_input='\n' + key.strip() + '\n', run_as_root=True) def _inject_net_into_fs(net, fs, execute=None): @@ -234,8 +235,8 @@ def _inject_net_into_fs(net, fs, execute=None): net is the contents of /etc/network/interfaces. """ netdir = os.path.join(os.path.join(fs, 'etc'), 'network') - utils.execute('sudo', 'mkdir', '-p', netdir) # existing dir doesn't matter - utils.execute('sudo', 'chown', 'root:root', netdir) - utils.execute('sudo', 'chmod', 755, netdir) + utils.execute('mkdir', '-p', netdir, run_as_root=True) + utils.execute('chown', 'root:root', netdir, run_as_root=True) + utils.execute('chmod', 755, netdir, run_as_root=True) netfile = os.path.join(netdir, 'interfaces') - utils.execute('sudo', 'tee', netfile, process_input=net) + utils.execute('tee', netfile, process_input=net, run_as_root=True) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index ec202a4ef..5053daf8f 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -607,9 +607,10 @@ class LibvirtConnection(driver.ComputeDriver): if virsh_output.startswith('/dev/'): LOG.info(_("cool, it's a device")) - out, err = utils.execute('sudo', 'dd', + out, err = utils.execute('dd', "if=%s" % virsh_output, 'iflag=nonblock', + run_as_root=True, check_exit_code=False) return out else: @@ -632,7 +633,7 @@ class LibvirtConnection(driver.ComputeDriver): console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') - utils.execute('sudo', 'chown', os.getuid(), console_log) + utils.execute('chown', os.getuid(), console_log, run_as_root=True) if FLAGS.libvirt_type == 'xen': # Xen is special @@ -914,7 +915,7 @@ class LibvirtConnection(driver.ComputeDriver): ' data into image %(img_id)s (%(e)s)') % locals()) if FLAGS.libvirt_type == 'uml': - utils.execute('sudo', 'chown', 'root', basepath('disk')) + utils.execute('chown', 'root', basepath('disk'), run_as_root=True) root_mount_device = 'vda' # FIXME for now. it's hard coded. local_mount_device = 'vdb' # FIXME for now. it's hard coded. diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 711b05bae..e243d4fa0 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -103,16 +103,18 @@ class LibvirtOpenVswitchDriver(VIFDriver): dev = "tap-%s" % vif_id iface_id = "nova-" + vif_id if not linux_net._device_exists(dev): - utils.execute('sudo', 'ip', 'tuntap', 'add', dev, 'mode', 'tap') - utils.execute('sudo', 'ip', 'link', 'set', dev, 'up') - utils.execute('sudo', 'ovs-vsctl', '--', '--may-exist', 'add-port', + utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap', + run_as_root=True) + utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True) + utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port', FLAGS.libvirt_ovs_bridge, dev, '--', 'set', 'Interface', dev, "external-ids:iface-id=%s" % iface_id, '--', 'set', 'Interface', dev, "external-ids:iface-status=active", '--', 'set', 'Interface', dev, - "external-ids:attached-mac=%s" % mapping['mac']) + "external-ids:attached-mac=%s" % mapping['mac'], + run_as_root=True) result = { 'script': '', @@ -126,9 +128,9 @@ class LibvirtOpenVswitchDriver(VIFDriver): vif_id = str(instance['id']) + "-" + str(network['id']) dev = "tap-%s" % vif_id try: - utils.execute('sudo', 'ovs-vsctl', 'del-port', - network['bridge'], dev) - utils.execute('sudo', 'ip', 'link', 'delete', dev) + utils.execute('ovs-vsctl', 'del-port', + network['bridge'], dev, run_as_root=True) + utils.execute('ip', 'link', 'delete', dev, run_as_root=True) except exception.ProcessExecutionError: LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 6d2340ccd..7b79e129c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -967,7 +967,7 @@ def _stream_disk(dev, image_type, virtual_size, image_file): offset = MBR_SIZE_BYTES _write_partition(virtual_size, dev) - utils.execute('sudo', 'chown', os.getuid(), '/dev/%s' % dev) + utils.execute('chown', os.getuid(), '/dev/%s' % dev, run_as_root=True) with open('/dev/%s' % dev, 'wb') as f: f.seek(offset) @@ -986,10 +986,11 @@ def _write_partition(virtual_size, dev): def execute(*cmd, **kwargs): return utils.execute(*cmd, **kwargs) - execute('sudo', 'parted', '--script', dest, 'mklabel', 'msdos') - execute('sudo', 'parted', '--script', dest, 'mkpart', 'primary', + execute('parted', '--script', dest, 'mklabel', 'msdos', run_as_root=True) + execute('parted', '--script', dest, 'mkpart', 'primary', '%ds' % primary_first, - '%ds' % primary_last) + '%ds' % primary_last, + run_as_root=True) LOG.debug(_('Writing partition table %s done.'), dest) @@ -1002,9 +1003,9 @@ def get_name_label_for_image(image): def _mount_filesystem(dev_path, dir): """mounts the device specified by dev_path in dir""" try: - out, err = utils.execute('sudo', 'mount', + out, err = utils.execute('mount', '-t', 'ext2,ext3', - dev_path, dir) + dev_path, dir, run_as_root=True) except exception.ProcessExecutionError as e: err = str(e) return err @@ -1056,7 +1057,7 @@ def _mounted_processing(device, key, net): disk.inject_data_into_fs(tmpdir, key, net, utils.execute) finally: - utils.execute('sudo', 'umount', dev_path) + utils.execute('umount', dev_path, run_as_root=True) else: LOG.info(_('Failed to mount filesystem (expected for ' 'non-linux instances): %s') % err) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 7821a4f7e..5d5eb824f 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -252,10 +252,10 @@ def _get_target(volume_id): volume_id) result = (None, None) try: - (r, _e) = utils.execute('sudo', 'iscsiadm', + (r, _e) = utils.execute('iscsiadm', '-m', 'discovery', '-t', 'sendtargets', - '-p', volume_ref['host']) + '-p', volume_ref['host'], run_as_root=True) except exception.ProcessExecutionError, exc: LOG.exception(exc) else: diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 23e845deb..c99534c07 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -65,14 +65,14 @@ class VolumeDriver(object): self._execute = execute self._sync_exec = sync_exec - def _try_execute(self, *command): + def _try_execute(self, *command, **kwargs): # NOTE(vish): Volume commands can partially fail due to timing, but # running them a second time on failure will usually # recover nicely. tries = 0 while True: try: - self._execute(*command) + self._execute(*command, **kwargs) return True except exception.ProcessExecutionError: tries = tries + 1 @@ -84,24 +84,26 @@ class VolumeDriver(object): def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" - out, err = self._execute('sudo', 'vgs', '--noheadings', '-o', 'name') + out, err = self._execute('vgs', '--noheadings', '-o', 'name', + run_as_root=True) volume_groups = out.split() if not FLAGS.volume_group in volume_groups: raise exception.Error(_("volume group %s doesn't exist") % FLAGS.volume_group) def _create_volume(self, volume_name, sizestr): - self._try_execute('sudo', 'lvcreate', '-L', sizestr, '-n', - volume_name, FLAGS.volume_group) + self._try_execute('lvcreate', '-L', sizestr, '-n', + volume_name, FLAGS.volume_group, run_as_root=True) def _copy_volume(self, srcstr, deststr, size_in_g): - self._execute('sudo', 'dd', 'if=%s' % srcstr, 'of=%s' % deststr, - 'count=%d' % (size_in_g * 1024), 'bs=1M') + self._execute('dd', 'if=%s' % srcstr, 'of=%s' % deststr, + 'count=%d' % (size_in_g * 1024), 'bs=1M', + run_as_root=True) def _volume_not_present(self, volume_name): path_name = '%s/%s' % (FLAGS.volume_group, volume_name) try: - self._try_execute('sudo', 'lvdisplay', path_name) + self._try_execute('lvdisplay', path_name, run_as_root=True) except Exception as e: # If the volume isn't present return True @@ -112,9 +114,10 @@ class VolumeDriver(object): # zero out old volumes to prevent data leaking between users # TODO(ja): reclaiming space should be done lazy and low priority self._copy_volume('/dev/zero', self.local_path(volume), size_in_g) - self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % + self._try_execute('lvremove', '-f', "%s/%s" % (FLAGS.volume_group, - self._escape_snapshot(volume['name']))) + self._escape_snapshot(volume['name'])), + run_as_root=True) def _sizestr(self, size_in_g): if int(size_in_g) == 0: @@ -147,10 +150,11 @@ class VolumeDriver(object): # TODO(yamahata): lvm can't delete origin volume only without # deleting derived snapshots. Can we do something fancy? - out, err = self._execute('sudo', 'lvdisplay', '--noheading', + out, err = self._execute('lvdisplay', '--noheading', '-C', '-o', 'Attr', '%s/%s' % (FLAGS.volume_group, - volume['name'])) + volume['name']), + run_as_root=True) # fake_execute returns None resulting unit test error if out: out = out.strip() @@ -162,10 +166,10 @@ class VolumeDriver(object): def create_snapshot(self, snapshot): """Creates a snapshot.""" orig_lv_name = "%s/%s" % (FLAGS.volume_group, snapshot['volume_name']) - self._try_execute('sudo', 'lvcreate', '-L', + self._try_execute('lvcreate', '-L', self._sizestr(snapshot['volume_size']), '--name', self._escape_snapshot(snapshot['name']), - '--snapshot', orig_lv_name) + '--snapshot', orig_lv_name, run_as_root=True) def delete_snapshot(self, snapshot): """Deletes a snapshot.""" @@ -233,13 +237,14 @@ class AOEDriver(VolumeDriver): blade_id) = self.db.volume_allocate_shelf_and_blade(context, volume['id']) self._try_execute( - 'sudo', 'vblade-persist', 'setup', + 'vblade-persist', 'setup', shelf_id, blade_id, FLAGS.aoe_eth_dev, "/dev/%s/%s" % (FLAGS.volume_group, - volume['name'])) + volume['name']), + run_as_root=True) # NOTE(vish): The standard _try_execute does not work here # because these methods throw errors if other # volumes on this host are in the process of @@ -248,28 +253,29 @@ class AOEDriver(VolumeDriver): # just wait a bit for the current volume to # be ready and ignore any errors. time.sleep(2) - self._execute('sudo', 'vblade-persist', 'auto', 'all', - check_exit_code=False) - self._execute('sudo', 'vblade-persist', 'start', 'all', - check_exit_code=False) + self._execute('vblade-persist', 'auto', 'all', + check_exit_code=False, run_as_root=True) + self._execute('vblade-persist', 'start', 'all', + check_exit_code=False, run_as_root=True) def remove_export(self, context, volume): """Removes an export for a logical volume.""" (shelf_id, blade_id) = self.db.volume_get_shelf_and_blade(context, volume['id']) - self._try_execute('sudo', 'vblade-persist', 'stop', - shelf_id, blade_id) - self._try_execute('sudo', 'vblade-persist', 'destroy', - shelf_id, blade_id) + self._try_execute('vblade-persist', 'stop', + shelf_id, blade_id, run_as_root=True) + self._try_execute('vblade-persist', 'destroy', + shelf_id, blade_id, run_as_root=True) def discover_volume(self, context, _volume): """Discover volume on a remote host.""" (shelf_id, blade_id) = self.db.volume_get_shelf_and_blade(context, _volume['id']) - self._execute('sudo', 'aoe-discover') - out, err = self._execute('sudo', 'aoe-stat', check_exit_code=False) + self._execute('aoe-discover', run_as_root=True) + out, err = self._execute('aoe-stat', check_exit_code=False, + run_as_root=True) device_path = 'e%(shelf_id)d.%(blade_id)d' % locals() if out.find(device_path) >= 0: return "/dev/etherd/%s" % device_path @@ -285,8 +291,8 @@ class AOEDriver(VolumeDriver): (shelf_id, blade_id) = self.db.volume_get_shelf_and_blade(context, volume_id) - cmd = ('sudo', 'vblade-persist', 'ls', '--no-header') - out, _err = self._execute(*cmd) + cmd = ('vblade-persist', 'ls', '--no-header') + out, _err = self._execute(*cmd, run_as_root=True) exported = False for line in out.split('\n'): param = line.split(' ') @@ -348,16 +354,18 @@ class ISCSIDriver(VolumeDriver): iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name']) volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name']) - self._sync_exec('sudo', 'ietadm', '--op', 'new', + self._sync_exec('ietadm', '--op', 'new', "--tid=%s" % iscsi_target, '--params', "Name=%s" % iscsi_name, + run_as_root=True, check_exit_code=False) - self._sync_exec('sudo', 'ietadm', '--op', 'new', + self._sync_exec('ietadm', '--op', 'new', "--tid=%s" % iscsi_target, '--lun=0', '--params', "Path=%s,Type=fileio" % volume_path, + run_as_root=True, check_exit_code=False) def _ensure_iscsi_targets(self, context, host): @@ -378,13 +386,13 @@ class ISCSIDriver(VolumeDriver): volume['host']) iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name']) volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name']) - self._execute('sudo', 'ietadm', '--op', 'new', + self._execute('ietadm', '--op', 'new', '--tid=%s' % iscsi_target, - '--params', 'Name=%s' % iscsi_name) - self._execute('sudo', 'ietadm', '--op', 'new', + '--params', 'Name=%s' % iscsi_name, run_as_root=True) + self._execute('ietadm', '--op', 'new', '--tid=%s' % iscsi_target, '--lun=0', '--params', - 'Path=%s,Type=fileio' % volume_path) + 'Path=%s,Type=fileio' % volume_path, run_as_root=True) def remove_export(self, context, volume): """Removes an export for a logical volume.""" @@ -399,18 +407,18 @@ class ISCSIDriver(VolumeDriver): try: # ietadm show will exit with an error # this export has already been removed - self._execute('sudo', 'ietadm', '--op', 'show', - '--tid=%s' % iscsi_target) + self._execute('ietadm', '--op', 'show', + '--tid=%s' % iscsi_target, run_as_root=True) except Exception as e: LOG.info(_("Skipping remove_export. No iscsi_target " + "is presently exported for volume: %d"), volume['id']) return - self._execute('sudo', 'ietadm', '--op', 'delete', + self._execute('ietadm', '--op', 'delete', '--tid=%s' % iscsi_target, - '--lun=0') - self._execute('sudo', 'ietadm', '--op', 'delete', - '--tid=%s' % iscsi_target) + '--lun=0', run_as_root=True) + self._execute('ietadm', '--op', 'delete', + '--tid=%s' % iscsi_target, run_as_root=True) def _do_iscsi_discovery(self, volume): #TODO(justinsb): Deprecate discovery and use stored info @@ -419,8 +427,9 @@ class ISCSIDriver(VolumeDriver): volume_name = volume['name'] - (out, _err) = self._execute('sudo', 'iscsiadm', '-m', 'discovery', - '-t', 'sendtargets', '-p', volume['host']) + (out, _err) = self._execute('iscsiadm', '-m', 'discovery', + '-t', 'sendtargets', '-p', volume['host'], + run_as_root=True) for target in out.splitlines(): if FLAGS.iscsi_ip_prefix in target and volume_name in target: return target @@ -483,10 +492,10 @@ class ISCSIDriver(VolumeDriver): return properties def _run_iscsiadm(self, iscsi_properties, iscsi_command): - (out, err) = self._execute('sudo', 'iscsiadm', '-m', 'node', '-T', + (out, err) = self._execute('iscsiadm', '-m', 'node', '-T', iscsi_properties['target_iqn'], '-p', iscsi_properties['target_portal'], - iscsi_command) + iscsi_command, run_as_root=True) LOG.debug("iscsiadm %s: stdout=%s stderr=%s" % (iscsi_command, out, err)) return (out, err) @@ -560,8 +569,8 @@ class ISCSIDriver(VolumeDriver): tid = self.db.volume_get_iscsi_target_num(context, volume_id) try: - self._execute('sudo', 'ietadm', '--op', 'show', - '--tid=%(tid)d' % locals()) + self._execute('ietadm', '--op', 'show', + '--tid=%(tid)d' % locals(), run_as_root=True) except exception.ProcessExecutionError, e: # Instances remount read-only in this case. # /etc/init.d/iscsitarget restart and rebooting nova-volume -- cgit From a77f10ee052fdafeb9d52407695719397c52e68d Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 5 Aug 2011 19:45:17 +0400 Subject: methods renamed --- nova/tests/test_nova_manage.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index ca9ffa222..9c6563f14 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -16,6 +16,7 @@ # under the License. import os +import sys TOPDIR = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -23,8 +24,10 @@ TOPDIR = os.path.normpath(os.path.join( os.pardir)) NOVA_MANAGE_PATH = os.path.join(TOPDIR, 'bin', 'nova-manage') +sys.dont_write_bytecode = True import imp -nova_manage = imp.load_source('nova_manage', NOVA_MANAGE_PATH) +nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH) +sys.dont_write_bytecode = False import netaddr from nova import context @@ -70,10 +73,10 @@ class FixedIpCommandsTestCase(test.TestCase): '10.0.0.100') self.assertEqual(address['reserved'], True) - def test_waste(self): + def test_unreserve(self): db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', {'reserved': True}) - self.commands.waste('10.0.0.100') + self.commands.unreserve('10.0.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), '10.0.0.100') self.assertEqual(address['reserved'], False) -- cgit From 9602a558b6be6e6812626b986c0f9557a3862fe6 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 11:59:14 -0400 Subject: glance image service pagination --- nova/api/openstack/images.py | 4 +-- nova/exception.py | 4 +++ nova/image/glance.py | 82 +++++++++++++++++++++++++++++--------------- 3 files changed, 61 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index c76738d30..b9bc83fde 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -143,7 +143,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.index(context, filters) + images = self._image_service.index(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=False) for image in images]) @@ -156,7 +156,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.detail(context, filters) + images = self._image_service.detail(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) diff --git a/nova/exception.py b/nova/exception.py index 68e6ac937..792e306c1 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -150,6 +150,10 @@ class NovaException(Exception): return self._error_string +class ImagePaginationFailed(NovaException): + message = _("Failed to paginate through images from image service") + + class VirtualInterfaceCreateException(NovaException): message = _("Virtual Interface creation failed") diff --git a/nova/image/glance.py b/nova/image/glance.py index 44a3c6f83..ccb1b7ec0 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -87,42 +87,70 @@ class GlanceImageService(service.BaseImageService): """Sets the client's auth token.""" self.client.set_auth_token(context.auth_token) - def index(self, context, filters=None, marker=None, limit=None): + def index(self, context, **kwargs): """Calls out to Glance for a list of images available.""" - # NOTE(sirp): We need to use `get_images_detailed` and not - # `get_images` here because we need `is_public` and `properties` - # included so we can filter by user - self._set_client_context(context) - filtered = [] - filters = filters or {} - if 'is_public' not in filters: - # NOTE(vish): don't filter out private images - filters['is_public'] = 'none' - image_metas = self.client.get_images_detailed(filters=filters, - marker=marker, - limit=limit) + params = self._extract_query_params(kwargs) + image_metas = self._get_images(context, **params) + + images = [] for image_meta in image_metas: + # NOTE(sirp): We need to use `get_images_detailed` and not + # `get_images` here because we need `is_public` and `properties` + # included so we can filter by user if self._is_image_available(context, image_meta): meta_subset = utils.subset_dict(image_meta, ('id', 'name')) - filtered.append(meta_subset) - return filtered + images.append(meta_subset) + return images - def detail(self, context, filters=None, marker=None, limit=None): + def detail(self, context, **kwargs): """Calls out to Glance for a list of detailed image information.""" - self._set_client_context(context) - filtered = [] - filters = filters or {} - if 'is_public' not in filters: - # NOTE(vish): don't filter out private images - filters['is_public'] = 'none' - image_metas = self.client.get_images_detailed(filters=filters, - marker=marker, - limit=limit) + params = self._extract_query_params(kwargs) + image_metas = self._get_images(context, **params) + + images = [] for image_meta in image_metas: if self._is_image_available(context, image_meta): base_image_meta = self._translate_to_base(image_meta) - filtered.append(base_image_meta) - return filtered + images.append(base_image_meta) + return images + + def _extract_query_params(self, params): + _params = {} + accepted_params = ('filters', 'marker', 'limit') + #'sort_key', 'sort_dir') + for param in accepted_params: + _params[param] = params.get(param) + + return _params + + def _get_images(self, context, **kwargs): + """Get image entitites from images service""" + self._set_client_context(context) + + # ensure filters is a dict + kwargs['filters'] = kwargs['filters'] or {} + # NOTE(vish): don't filter out private images + kwargs['filters'].setdefault('is_public', 'none') + + return self._fetch_images(self.client.get_images_detailed, **kwargs) + + def _fetch_images(self, fetch_func, **kwargs): + """Paginate through results from glance server""" + images = fetch_func(**kwargs) + + for image in images: + yield image + else: + # break out of recursive loop to end pagination + return + + try: + # attempt to advance the marker in order to fetch next page + kwargs['marker'] = images[-1]['id'] + except KeyError: + raise exception.ImagePaginationFailed() + + self._fetch_images(fetch_func, **kwargs) def show(self, context, image_id): """Returns a dict with image data for the given opaque image id.""" -- cgit From 149153085027237c343cc325e163979f1cd31a21 Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 16:22:21 +0000 Subject: Moving from assertDictEqual to assertDictMatch --- nova/tests/api/openstack/test_server_actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 005923f4e..7e24d24fd 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -986,7 +986,7 @@ class TestServerActionXMLDeserializerV11(test.TestCase): ], }, } - self.assertDictEqual(request['body'], expected) + self.assertDictMatch(request['body'], expected) def test_rebuild_minimum(self): serial_request = """ @@ -999,7 +999,7 @@ class TestServerActionXMLDeserializerV11(test.TestCase): "imageRef": "http://localhost/images/1", }, } - self.assertDictEqual(request['body'], expected) + self.assertDictMatch(request['body'], expected) def test_rebuild_no_imageRef(self): serial_request = """ -- cgit From 681d3e2bfac2aa8e19cb393591b99efcbcdd8230 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 14:15:53 -0400 Subject: re-enabling sort_key/sort_dir and fixing filters line --- nova/image/glance.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index ccb1b7ec0..23749e560 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -116,10 +116,11 @@ class GlanceImageService(service.BaseImageService): def _extract_query_params(self, params): _params = {} - accepted_params = ('filters', 'marker', 'limit') - #'sort_key', 'sort_dir') + accepted_params = ('filters', 'marker', 'limit', + 'sort_key', 'sort_dir') for param in accepted_params: - _params[param] = params.get(param) + if param in params: + _params[param] = params.get(param) return _params @@ -128,7 +129,7 @@ class GlanceImageService(service.BaseImageService): self._set_client_context(context) # ensure filters is a dict - kwargs['filters'] = kwargs['filters'] or {} + kwargs['filters'] = kwargs.get('filters', {}) # NOTE(vish): don't filter out private images kwargs['filters'].setdefault('is_public', 'none') -- cgit From a3e618bdc64f5e2cbb62fcf4a5b2df0e437c4fd9 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 15:02:18 -0400 Subject: fixing filters get --- nova/image/glance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 23749e560..da93f0d1c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -129,7 +129,7 @@ class GlanceImageService(service.BaseImageService): self._set_client_context(context) # ensure filters is a dict - kwargs['filters'] = kwargs.get('filters', {}) + kwargs['filters'] = kwargs.get('filters') or {} # NOTE(vish): don't filter out private images kwargs['filters'].setdefault('is_public', 'none') -- cgit From f03c926a7d28ee35789048ea53c36cd452ed3571 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 5 Aug 2011 14:28:22 -0500 Subject: Allow actions queries by UUID and PEP8 fixes. --- nova/api/direct.py | 1 + nova/api/openstack/common.py | 3 ++- nova/db/sqlalchemy/api.py | 10 ++++++++-- nova/tests/test_image.py | 2 ++ nova/tests/test_xenapi.py | 1 - 5 files changed, 13 insertions(+), 4 deletions(-) (limited to 'nova') 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..4548c2c75 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 diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ce12ba4e0..f469dc0e5 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1426,9 +1426,14 @@ def instance_action_create(context, values): def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() + + if utils.is_uuid_like(instance_id): + instance = instance_get_by_uuid(context, instance_id, session) + instance_id = instance.id + return session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all() + all() ################### @@ -3301,7 +3306,8 @@ def instance_type_extra_specs_delete(context, instance_type_id, key): @require_context -def instance_type_extra_specs_get_item(context, instance_type_id, key, session=None): +def instance_type_extra_specs_get_item(context, instance_type_id, key, + session=None): if not session: session = get_session() diff --git a/nova/tests/test_image.py b/nova/tests/test_image.py index 5ec8812f3..9680d6f2b 100644 --- a/nova/tests/test_image.py +++ b/nova/tests/test_image.py @@ -22,6 +22,7 @@ from nova import exception from nova import test import nova.image + class _ImageTestCase(test.TestCase): def setUp(self): super(_ImageTestCase, self).setUp() @@ -126,6 +127,7 @@ class _ImageTestCase(test.TestCase): index = self.image_service.index(self.context) self.assertEquals(len(index), 0) + class FakeImageTestCase(_ImageTestCase): def setUp(self): super(FakeImageTestCase, self).setUp() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8048e5341..dfc1eeb0a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -767,7 +767,6 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_revert_migrate(self): instance = db.instance_create(self.context, self.values) self.called = False -- 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') 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 9633e9877c7836c18c30b51c8494abfb025e64ca Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:14:15 +0000 Subject: Adding flag around image-create for v1.0 --- nova/api/openstack/__init__.py | 3 +++ nova/api/openstack/images.py | 6 ++++++ nova/tests/api/openstack/test_images.py | 10 ++++++++++ 3 files changed, 19 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d6a98c2cd..4d49df2ad 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -50,6 +50,9 @@ FLAGS = flags.FLAGS flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') +flags.DEFINE_bool('allow_instance_snapshots', + True, + 'When True, this API service will permit instance snapshot operations.') class FaultWrapper(base_wsgi.Middleware): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b9bc83fde..7b738e1f3 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -108,6 +108,12 @@ class ControllerV10(Controller): def create(self, req, body): """Snapshot a server instance and save the image.""" + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + try: image = body["image"] except (KeyError, TypeError): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8e2e3f390..38495bbe7 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -1049,6 +1049,16 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(400, response.status_int) + def test_create_image_snapshots_disabled(self): + self.flags(allow_instance_snapshots=False) + body = dict(image=dict(serverId='123', name='Snapshot 1')) + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + @classmethod def _make_image_fixtures(cls): image_id = 123 -- 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') 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 c49e99a7fc590c2dde6125843d904895ca8861a3 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:29:28 +0000 Subject: Disable flag for V1 Openstack API --- nova/api/openstack/servers.py | 6 ++++++ nova/tests/api/openstack/test_server_actions.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1051ba571..391c7d644 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -691,6 +691,12 @@ class ControllerV11(Controller): def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + entity = input_dict.get("createImage", {}) try: diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 7e24d24fd..bf18bc1b0 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -458,6 +458,7 @@ class ServerActionsTestV11(test.TestCase): self.service.delete_all() self.sent_to_glance = {} fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) + self.flags(allow_instance_snapshots=True) def tearDown(self): self.stubs.UnsetAll() @@ -775,6 +776,23 @@ class ServerActionsTestV11(test.TestCase): location = response.headers['Location'] self.assertEqual('http://localhost/v1.1/images/123', location) + def test_create_image_snapshots_disabled(self): + """Don't permit a snapshot if the allow_instance_snapshots flag is + False + """ + self.flags(allow_instance_snapshots=False) + body = { + 'createImage': { + 'name': 'Snapshot 1', + }, + } + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + def test_create_image_with_metadata(self): body = { 'createImage': { -- cgit From bdabdd50845279cbca11f510dd5da6a5aa110528 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 22:56:08 +0000 Subject: Using decorator for snapshots enabled check --- nova/api/openstack/common.py | 14 ++++++++++++++ nova/api/openstack/images.py | 7 +------ nova/api/openstack/servers.py | 7 +------ 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 4548c2c75..ec9368140 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import re import urlparse from xml.dom import minidom @@ -280,3 +281,16 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): def default(self, *args, **kwargs): return '' + + +def check_snapshots_enabled(f): + @functools.wraps(f) + def inner(*args, **kwargs): + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance Snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + return f(*args, **kwargs) + return inner + diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 7b738e1f3..0aabb9e56 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -106,14 +106,9 @@ class Controller(object): class ControllerV10(Controller): """Version 1.0 specific controller logic.""" + @common.check_snapshots_enabled def create(self, req, body): """Snapshot a server instance and save the image.""" - if not FLAGS.allow_instance_snapshots: - LOG.warn(_('Rejecting snapshot request, snapshots currently' - ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") - raise webob.exc.HTTPBadRequest(explanation=msg) - try: image = body["image"] except (KeyError, TypeError): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 391c7d644..4d6518598 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -689,14 +689,9 @@ class ControllerV11(Controller): return webob.Response(status_int=202) + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" - if not FLAGS.allow_instance_snapshots: - LOG.warn(_('Rejecting snapshot request, snapshots currently' - ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") - raise webob.exc.HTTPBadRequest(explanation=msg) - entity = input_dict.get("createImage", {}) try: -- 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') 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 b15535a20b7f717aa23f5bc6d695e574bb86c407 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 23:26:08 +0000 Subject: Adding check to stub method --- nova/api/openstack/common.py | 2 +- nova/api/openstack/servers.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index ec9368140..375304930 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -289,7 +289,7 @@ def check_snapshots_enabled(f): if not FLAGS.allow_instance_snapshots: LOG.warn(_('Rejecting snapshot request, snapshots currently' ' disabled')) - msg = _("Instance Snapshots are not permitted at this time.") + msg = _("Instance snapshots are not permitted at this time.") raise webob.exc.HTTPBadRequest(explanation=msg) return f(*args, **kwargs) return inner diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4d6518598..f1a27a98c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -240,6 +240,7 @@ class Controller(object): resp.headers['Location'] = image_ref return resp + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() -- cgit From 8c6ccfd51698bffe0d56193bc4137ad80708e6d3 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 5 Aug 2011 16:41:03 -0700 Subject: zone_aware_scheduler classes couldn't build instances due to a change to compute api's create_db_entry_for_new_instance call. now passing image argument down to the scheduler and through to the call. updated a existing test to cover this --- nova/compute/api.py | 4 ++++ nova/scheduler/zone_aware_scheduler.py | 3 ++- nova/tests/scheduler/test_zone_aware_scheduler.py | 27 ++++++++++++++++++----- 3 files changed, 28 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 80d54d029..752f29384 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -360,6 +360,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, instance_id=None, num_instances=1): """Send the run_instance request to the schedulers for processing.""" pid = context.project_id @@ -373,6 +374,7 @@ class API(base.Base): filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter' request_spec = { + 'image': image, 'instance_properties': base_options, 'instance_type': instance_type, 'filter': filter_class, @@ -415,6 +417,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, num_instances=num_instances) return base_options['reservation_id'] @@ -463,6 +466,7 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, + image, instance_id=instance_id) return [dict(x.iteritems()) for x in instances] diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d99d7214c..a47bf7fe7 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -58,12 +58,13 @@ class ZoneAwareScheduler(driver.Scheduler): """Create the requested resource in this Zone.""" host = build_plan_item['hostname'] base_options = request_spec['instance_properties'] + image = request_spec['image'] # TODO(sandy): I guess someone needs to add block_device_mapping # support at some point? Also, OS API has no concept of security # groups. instance = compute_api.API().create_db_entry_for_new_instance(context, - base_options, None, []) + image, base_options, None, []) instance_id = instance['id'] kwargs['instance_id'] = instance_id diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 7833028c3..788efca52 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -21,7 +21,9 @@ import json import nova.db from nova import exception +from nova import rpc from nova import test +from nova.compute import api as compute_api from nova.scheduler import driver from nova.scheduler import zone_aware_scheduler from nova.scheduler import zone_manager @@ -114,7 +116,7 @@ def fake_provision_resource_from_blob(context, item, instance_id, def fake_decrypt_blob_returns_local_info(blob): - return {'foo': True} # values aren't important. + return {'hostname': 'foooooo'} # values aren't important. def fake_decrypt_blob_returns_child_info(blob): @@ -283,14 +285,29 @@ class ZoneAwareSchedulerTestCase(test.TestCase): global was_called sched = FakeZoneAwareScheduler() was_called = False + + def fake_create_db_entry_for_new_instance(self, context, + image, base_options, security_group, + block_device_mapping, num=1): + global was_called + was_called = True + # return fake instances + return {'id': 1, 'uuid': 'f874093c-7b17-49c0-89c3-22a5348497f9'} + + def fake_rpc_cast(*args, **kwargs): + pass + self.stubs.Set(sched, '_decrypt_blob', fake_decrypt_blob_returns_local_info) - self.stubs.Set(sched, '_provision_resource_locally', - fake_provision_resource_locally) + self.stubs.Set(compute_api.API, + 'create_db_entry_for_new_instance', + fake_create_db_entry_for_new_instance) + self.stubs.Set(rpc, 'cast', fake_rpc_cast) - request_spec = {'blob': "Non-None blob data"} + build_plan_item = {'blob': "Non-None blob data"} + request_spec = {'image': {}, 'instance_properties': {}} - sched._provision_resource_from_blob(None, request_spec, 1, + sched._provision_resource_from_blob(None, build_plan_item, 1, request_spec, {}) self.assertTrue(was_called) -- cgit From 7a5bb39ef11d630df26f2fcfbf249f0c34e9fa55 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 5 Aug 2011 18:51:17 -0500 Subject: Pep8 fix --- nova/api/openstack/common.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 375304930..5226cdf9a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -293,4 +293,3 @@ def check_snapshots_enabled(f): raise webob.exc.HTTPBadRequest(explanation=msg) return f(*args, **kwargs) return inner - -- cgit From 27a77fbc2651381d9663064a363105f803781924 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 8 Aug 2011 09:30:56 +0000 Subject: Save exception and re-raise that instead of depending on thread local exception that may have been clobbered by intermediate processing --- nova/exception.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index 792e306c1..b017c8d87 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -96,6 +96,10 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, try: return f(*args, **kw) except Exception, e: + # Save exception since it can be clobbered during processing + # below before we can re-raise + exc_info = sys.exc_info() + if notifier: payload = dict(args=args, exception=e) payload.update(kw) @@ -122,7 +126,9 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, LOG.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) - raise + + # re-raise original exception since it may have been clobbered + raise exc_info[0], exc_info[1], exc_info[2] return wraps(f)(wrapped) return inner -- 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 +++++++----------- nova/virt/xenapi/vmops.py | 2 +- nova/virt/xenapi_conn.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) (limited to 'nova') 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") diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index df90b62c4..2a5cf8b5e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1032,7 +1032,7 @@ class VMOps(object): return 'http://fakeajaxconsole/fake_url' def host_power_action(self, host, action): - """Reboots, shuts down or powers up the host.""" + """Reboots or shuts down the host.""" args = {"action": json.dumps(action)} methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"} json_resp = self._call_xenhost(methods[action], args) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 720c9fd58..2a6a97faf 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -333,8 +333,17 @@ class XenAPIConnection(driver.ComputeDriver): return self.HostState.get_host_stats(refresh=refresh) def host_power_action(self, host, action): - """Reboots, shuts down or powers up the host.""" - return self._vmops.host_power_action(host, action) + """The only valid values for 'action' on XenServer are 'reboot' or + 'shutdown', even though the API also accepts 'startup'. As this is + not technically possible on XenServer, since the host is the same + physical machine as the hypervisor, if this is requested, we need to + raise an exception. + """ + if action in ("reboot", "shutdown"): + return self._vmops.host_power_action(host, action) + else: + msg = _("Host startup on XenServer is not supported.") + raise NotImplementedError(msg) def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" -- 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 +- nova/tests/test_hosts.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') 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"})] diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index cd22571e6..a724db9da 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -94,12 +94,11 @@ class HostTestCase(test.TestCase): self.assertEqual(result_c2["status"], "disabled") def test_host_startup(self): - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.startup, - self.req, "host_c1") + result = self.controller.startup(self.req, "host_c1") + self.assertEqual(result["power_action"], "startup") def test_host_shutdown(self): result = self.controller.shutdown(self.req, "host_c1") - print "RES", result self.assertEqual(result["power_action"], "shutdown") def test_host_reboot(self): -- cgit From 586359f792cb32210f83046e46a0cdb85b319fcd Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 14:51:42 +0000 Subject: Cleaned up some old code added by the last merge --- nova/compute/manager.py | 8 +------- nova/virt/fake.py | 4 ---- nova/virt/hyperv.py | 4 ---- nova/virt/libvirt/connection.py | 4 ---- nova/virt/vmwareapi_conn.py | 4 ---- nova/virt/xenapi/vmops.py | 11 ----------- nova/virt/xenapi_conn.py | 4 ---- 7 files changed, 1 insertion(+), 38 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 4908cba97..ecfbd3908 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1,4 +1,4 @@ -#: tabstop=4 shiftwidth=4 softtabstop=4 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. @@ -969,12 +969,6 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_power_state(self, context, instance_id=None, host=None, - power_state=None): - """Turns the specified host on/off, or reboots the host.""" - return self.driver.set_power_state(host, power_state) - @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/virt/fake.py b/nova/virt/fake.py index a811edf7a..89ad20494 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -519,7 +519,3 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 6524e1739..ae30c62f0 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -506,7 +506,3 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index af5221f55..7655bf386 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1569,7 +1569,3 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass - - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index cfa4cb418..aaa384374 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -199,10 +199,6 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - pass - def plug_vifs(self, instance, network_info): """Plugs in VIFs to networks.""" self._vmops.plug_vifs(instance, network_info) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 2a5cf8b5e..b549b33d1 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1050,17 +1050,6 @@ class VMOps(object): return xenapi_resp.details[-1] return resp["status"] - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - args = {"power_state": power_state} - xenapi_resp = self._call_xenhost("set_power_state", args) - try: - resp = json.loads(xenapi_resp) - except TypeError as e: - # Already logged; return the message - return xenapi_resp.details[-1] - return resp["power_state"] - def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 2a6a97faf..a1c9a1e30 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -349,10 +349,6 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) - def set_power_state(self, host, power_state): - """Reboots, shuts down or starts up the host.""" - return self._vmops.set_power_state(host, power_state) - class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From b23387ef7a0024ac11e0970e3b76fa3441e30a9c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 8 Aug 2011 17:34:42 +0000 Subject: Review fixes --- nova/compute/api.py | 4 ++-- nova/compute/manager.py | 6 ++---- nova/virt/xenapi/vmops.py | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index d5d66fa57..d2c08678b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -997,12 +997,12 @@ class API(base.Base): def set_host_enabled(self, context, host, enabled): """Sets the specified host's ability to accept new instances.""" return self._call_compute_message("set_host_enabled", context, - instance_id=None, host=host, params={"enabled": enabled}) + host=host, params={"enabled": enabled}) def host_power_action(self, context, host, action): """Reboots, shuts down or powers up the host.""" return self._call_compute_message("host_power_action", context, - instance_id=None, host=host, params={"action": action}) + host=host, params={"action": action}) @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ecfbd3908..cb6617c33 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -958,14 +958,12 @@ class ComputeManager(manager.SchedulerDependentManager): result)) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def host_power_action(self, context, instance_id=None, host=None, - action=None): + def host_power_action(self, context, host=None, action=None): """Reboots, shuts down or powers up the host.""" return self.driver.host_power_action(host, action) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) - def set_host_enabled(self, context, instance_id=None, host=None, - enabled=None): + def set_host_enabled(self, context, host=None, enabled=None): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b549b33d1..b913e764e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1045,7 +1045,7 @@ class VMOps(object): xenapi_resp = self._call_xenhost("set_host_enabled", args) try: resp = json.loads(xenapi_resp) - except TypeError as e: + except TypeError as e: # Already logged; return the message return xenapi_resp.details[-1] return resp["status"] -- 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') 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 From 9788cddbf7833a82fc5589dd5f2869a309d1f657 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 8 Aug 2011 19:28:42 +0000 Subject: Import sys as well --- nova/exception.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/exception.py b/nova/exception.py index b017c8d87..a87728fff 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -25,6 +25,7 @@ SHOULD include dedicated exception logging. """ from functools import wraps +import sys from nova import log as logging -- cgit From 4c7a33ee907ae7abcff020c75dde8ed320fe7aeb Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Mon, 8 Aug 2011 15:30:44 -0400 Subject: assert that vmops.revert_migration is called --- nova/tests/test_xenapi.py | 25 ++++++++++++++++++------- nova/tests/xenapi/stubs.py | 4 ---- 2 files changed, 18 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8e3c88f8e..34aba140b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -637,6 +637,24 @@ class XenAPIVMTestCase(test.TestCase): # Ensure that it will not unrescue a non-rescued instance. self.assertRaises(Exception, conn.unrescue, instance, None) + def test_revert_migration(self): + instance = self._create_instance() + + class VMOpsMock(): + + def __init__(self): + self.revert_migration_called = False + + def revert_migration(self, instance): + self.revert_migration_called = True + + stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) + + conn = xenapi_conn.get_connection(False) + conn._vmops = VMOpsMock() + conn.revert_migration(instance) + self.assertTrue(conn._vmops.revert_migration_called) + def _create_instance(self, instance_id=1, spawn=True): """Creates and spawns a test instance.""" stubs.stubout_loopingcall_start(self.stubs) @@ -669,13 +687,6 @@ class XenAPIVMTestCase(test.TestCase): self.conn.spawn(instance, network_info) return instance - def test_revert_migration(self): - instance = self._create_instance() - stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) - stubs.stubout_loopingcall_start(self.stubs) - conn = xenapi_conn.get_connection(False) - conn.revert_migration(instance) - class XenAPIDiffieHellmanTestCase(test.TestCase): """Unit tests for Diffie-Hellman code.""" diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 195e4d038..66c79d465 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -230,14 +230,10 @@ def stub_out_vm_methods(stubs): def fake_spawn_rescue(self, inst): inst._rescue = False - def fake_revert_migration(self, inst): - pass - stubs.Set(vmops.VMOps, "_shutdown", fake_shutdown) stubs.Set(vmops.VMOps, "_acquire_bootlock", fake_acquire_bootlock) stubs.Set(vmops.VMOps, "_release_bootlock", fake_release_bootlock) stubs.Set(vmops.VMOps, "spawn_rescue", fake_spawn_rescue) - stubs.Set(vmops.VMOps, "revert_migration", fake_revert_migration) class FakeSessionForVolumeTests(fake.SessionBase): -- cgit From 2a8cff40af58d6d2b2fc3a818816eb2a913cccfb Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 11:22:32 +0100 Subject: Fix ajaxterm's use of shell=True, prevent vmops.py from running its own version of utils.execute --- nova/utils.py | 4 ---- nova/virt/libvirt/connection.py | 6 +++--- nova/virt/xenapi/vmops.py | 26 ++++++++------------------ 3 files changed, 11 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index 0820a2bde..7f27c5fd5 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -138,8 +138,6 @@ def execute(*cmd, **kwargs): :delay_on_retry True | False. Defaults to True. If set to True, wait a short amount of time before retrying. :attempts How many times to retry cmd. - :shell True | False. Defaults to False. If set to True, - Popen command is called shell=True. :run_as_root True | False. Defaults to False. If set to True, the command is prefixed by the command specified in the sudo_helper FLAG. @@ -153,7 +151,6 @@ def execute(*cmd, **kwargs): check_exit_code = kwargs.pop('check_exit_code', 0) delay_on_retry = kwargs.pop('delay_on_retry', True) attempts = kwargs.pop('attempts', 1) - shell = kwargs.pop('shell', False) run_as_root = kwargs.pop('run_as_root', False) if len(kwargs): raise exception.Error(_('Got unknown keyword args ' @@ -175,7 +172,6 @@ def execute(*cmd, **kwargs): stdin=_PIPE, stdout=_PIPE, stderr=_PIPE, - shell=shell, env=env) result = None if process_input is not None: diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 5053daf8f..a01fc3317 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -681,10 +681,10 @@ class LibvirtConnection(driver.ComputeDriver): ajaxterm_cmd = 'sudo socat - %s' \ % get_pty_for_instance(instance['name']) - cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ - % (utils.novadir(), ajaxterm_cmd, token, port) + cmd = ['%s/tools/ajaxterm/ajaxterm.py' % utils.novadir(), + '--command', ajaxterm_cmd, '-t', token, '-p', port] - utils.execute(cmd, shell=True) + utils.execute(cmd) return {'token': token, 'host': host, 'port': port} def get_host_ip_addr(self): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a78413370..230ecd560 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1320,12 +1320,6 @@ class VMOps(object): ######################################################################## -def _runproc(cmd): - pipe = subprocess.PIPE - return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, - stderr=pipe, close_fds=True) - - class SimpleDH(object): """ This class wraps all the functionality needed to implement @@ -1382,22 +1376,18 @@ class SimpleDH(object): mpi = M2Crypto.m2.bn_to_mpi(bn) return mpi - def _run_ssl(self, text, extra_args=None): - if not extra_args: - extra_args = '' - cmd = 'enc -aes-128-cbc -A -a -pass pass:%s -nosalt %s' % ( - self._shared, extra_args) - proc = _runproc('openssl %s' % cmd) - proc.stdin.write(text) - proc.stdin.close() - proc.wait() - err = proc.stderr.read() + def _run_ssl(self, text, decrypt=False): + cmd = ['openssl', 'aes-128-cbc', '-A', '-a', '-pass', + 'pass:%s' % self._shared, '-nosalt'] + if decrypt: + cmd.append('-d') + out, err = utils.execute(*cmd, process_input=text) if err: raise RuntimeError(_('OpenSSL error: %s') % err) - return proc.stdout.read() + return out def encrypt(self, text): return self._run_ssl(text).strip('\n') def decrypt(self, text): - return self._run_ssl(text, '-d') + return self._run_ssl(text, decrypt=True) -- cgit From ed9ea0848e4c8e8220a7f3bc175d7855c88c84d0 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 12:05:05 +0100 Subject: Use close_fds by default since it's good for you --- nova/utils.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index 7f27c5fd5..d691481f8 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -172,6 +172,7 @@ def execute(*cmd, **kwargs): stdin=_PIPE, stdout=_PIPE, stderr=_PIPE, + close_fds=True, env=env) result = None if process_input is not None: -- cgit From 7e810e1f266daaa63167ea8412dc0416e88f688f Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 12:21:28 +0100 Subject: Fix usage of sudo -E and addl_env in dnsmasq/radvd calls, remove addl_env support, fix fake_execute allowed kwargs --- nova/network/linux_net.py | 63 +++++++++++++++++++---------------------------- nova/tests/fake_utils.py | 12 ++++++--- nova/utils.py | 8 +----- 3 files changed, 35 insertions(+), 48 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 3c62c75cd..a8af4af57 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -617,11 +617,26 @@ def update_dhcp(context, network_ref): else: LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid) - # FLAGFILE and DNSMASQ_INTERFACE in env - env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, - 'DNSMASQ_INTERFACE': network_ref['bridge']} - command = _dnsmasq_cmd(network_ref) - _execute(*command, addl_env=env) + cmd = ['FLAGFILE="%s"' % FLAGS.dhcpbridge_flagfile, + 'DNSMASQ_INTERFACE="%s"' % network_ref['bridge'], + 'dnsmasq', + '--strict-order', + '--bind-interfaces', + '--interface=%s' % network_ref['bridge'], + '--conf-file=%s' % FLAGS.dnsmasq_config_file, + '--domain=%s' % FLAGS.dhcp_domain, + '--pid-file=%s' % _dhcp_file(network_ref['bridge'], 'pid'), + '--listen-address=%s' % network_ref['dhcp_server'], + '--except-interface=lo', + '--dhcp-range=%s,static,120s' % network_ref['dhcp_start'], + '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])), + '--dhcp-hostsfile=%s' % _dhcp_file(network_ref['bridge'], 'conf'), + '--dhcp-script=%s' % FLAGS.dhcpbridge, + '--leasefile-ro'] + if FLAGS.dns_server: + cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server] + + _execute(*cmd, run_as_root=True) @utils.synchronized('radvd_start') @@ -659,8 +674,12 @@ interface %s LOG.debug(_('killing radvd threw %s'), exc) else: LOG.debug(_('Pid %d is stale, relaunching radvd'), pid) - command = _ra_cmd(network_ref) - _execute(*command) + + cmd = ['radvd', + '-C', '%s' % _ra_file(network_ref['bridge'], 'conf'), + '-p', '%s' % _ra_file(network_ref['bridge'], 'pid')] + + _execute(*cmd, run_as_root=True) def _host_lease(fixed_ip_ref): @@ -704,36 +723,6 @@ def _device_exists(device): return not err -def _dnsmasq_cmd(net): - """Builds dnsmasq command.""" - cmd = ['sudo', '-E', 'dnsmasq', - '--strict-order', - '--bind-interfaces', - '--interface=%s' % net['bridge'], - '--conf-file=%s' % FLAGS.dnsmasq_config_file, - '--domain=%s' % FLAGS.dhcp_domain, - '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), - '--listen-address=%s' % net['dhcp_server'], - '--except-interface=lo', - '--dhcp-range=%s,static,120s' % net['dhcp_start'], - '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(net['cidr'])), - '--dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'), - '--dhcp-script=%s' % FLAGS.dhcpbridge, - '--leasefile-ro'] - if FLAGS.dns_server: - cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server] - return cmd - - -def _ra_cmd(net): - """Builds radvd command.""" - cmd = ['sudo', '-E', 'radvd', -# '-u', 'nobody', - '-C', '%s' % _ra_file(net['bridge'], 'conf'), - '-p', '%s' % _ra_file(net['bridge'], 'pid')] - return cmd - - def _stop_dnsmasq(network): """Stops the dnsmasq instance for a given network.""" pid = _dnsmasq_pid_for(network) diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index be59970c9..72f79921e 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -63,9 +63,11 @@ def fake_execute(*cmd_parts, **kwargs): """ global _fake_execute_repliers - process_input = kwargs.get('process_input', None) - addl_env = kwargs.get('addl_env', None) - check_exit_code = kwargs.get('check_exit_code', 0) + process_input = kwargs.pop('process_input', None) + check_exit_code = kwargs.pop('check_exit_code', 0) + delay_on_retry = kwargs.pop('delay_on_retry', True) + attempts = kwargs.pop('attempts', 1) + run_as_root = kwargs.pop('run_as_root', False) cmd_str = ' '.join(str(part) for part in cmd_parts) LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd_str) @@ -87,7 +89,9 @@ def fake_execute(*cmd_parts, **kwargs): # Alternative is a function, so call it reply = reply_handler(cmd_parts, process_input=process_input, - addl_env=addl_env, + delay_on_retry=delay_on_retry, + attempts=attempts, + run_as_root=run_as_root, check_exit_code=check_exit_code) except exception.ProcessExecutionError as e: LOG.debug(_('Faked command raised an exception %s' % str(e))) diff --git a/nova/utils.py b/nova/utils.py index d691481f8..dc81abdb9 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -132,7 +132,6 @@ def execute(*cmd, **kwargs): :cmd Passed to subprocess.Popen. :process_input Send to opened process. - :addl_env Added to the processes env. :check_exit_code Defaults to 0. Raise exception.ProcessExecutionError unless program exits with this code. :delay_on_retry True | False. Defaults to True. If set to True, wait a @@ -147,7 +146,6 @@ def execute(*cmd, **kwargs): """ process_input = kwargs.pop('process_input', None) - addl_env = kwargs.pop('addl_env', None) check_exit_code = kwargs.pop('check_exit_code', 0) delay_on_retry = kwargs.pop('delay_on_retry', True) attempts = kwargs.pop('attempts', 1) @@ -164,16 +162,12 @@ def execute(*cmd, **kwargs): attempts -= 1 try: LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd)) - env = os.environ.copy() - if addl_env: - env.update(addl_env) _PIPE = subprocess.PIPE # pylint: disable=E1101 obj = subprocess.Popen(cmd, stdin=_PIPE, stdout=_PIPE, stderr=_PIPE, - close_fds=True, - env=env) + close_fds=True) result = None if process_input is not None: result = obj.communicate(process_input) -- cgit From 446fb79eb0025ce50cac9fc5496d4e4840134bce Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 13:30:06 +0100 Subject: Command args can be a tuple, convert them to list --- nova/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index dc81abdb9..80044e37d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -155,7 +155,7 @@ def execute(*cmd, **kwargs): 'to utils.execute: %r') % kwargs) if run_as_root: - cmd = shlex.split(FLAGS.sudo_helper) + cmd + cmd = shlex.split(FLAGS.sudo_helper) + list(cmd) cmd = map(str, cmd) while attempts > 0: -- cgit From 142f072cc3416f88dd0af757b17d7ddd473c5e20 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 13:34:54 +0100 Subject: Remove old commented line --- nova/network/linux_net.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index a8af4af57..8b7b59f55 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -492,7 +492,6 @@ def ensure_bridge(bridge, interface, net_attrs=None): LOG.debug(_('Starting Bridge interface for %s'), interface) _execute('brctl', 'addbr', bridge, run_as_root=True) _execute('brctl', 'setfd', bridge, 0, run_as_root=True) - # _execute('sudo brctl setageing %s 10' % bridge) _execute('brctl', 'stp', bridge, 'off', run_as_root=True) _execute('ip', 'link', 'set', bridge, 'up', run_as_root=True) if net_attrs: -- cgit From 238122f55fe70eedcd670714d80e6d2c10c4f3ab Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 13:47:55 +0100 Subject: Minor fix to reduce diff --- nova/tests/fake_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index 72f79921e..84ab641ea 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -63,11 +63,11 @@ def fake_execute(*cmd_parts, **kwargs): """ global _fake_execute_repliers - process_input = kwargs.pop('process_input', None) - check_exit_code = kwargs.pop('check_exit_code', 0) - delay_on_retry = kwargs.pop('delay_on_retry', True) - attempts = kwargs.pop('attempts', 1) - run_as_root = kwargs.pop('run_as_root', False) + process_input = kwargs.get('process_input', None) + check_exit_code = kwargs.get('check_exit_code', 0) + delay_on_retry = kwargs.get('delay_on_retry', True) + attempts = kwargs.get('attempts', 1) + run_as_root = kwargs.get('run_as_root', False) cmd_str = ' '.join(str(part) for part in cmd_parts) LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd_str) -- cgit From c1ac67222e18c5fe74a52aa864231d5311374816 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 9 Aug 2011 14:11:15 +0100 Subject: Rename sudo_helper FLAG into root_helper --- nova/flags.py | 2 +- nova/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index e30c0d6be..35209aa24 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -393,5 +393,5 @@ DEFINE_bool('start_guests_on_host_boot', False, DEFINE_bool('resume_guests_state_on_host_boot', False, 'Whether to start guests, that was running before the host reboot') -DEFINE_string('sudo_helper', 'sudo', +DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') diff --git a/nova/utils.py b/nova/utils.py index 80044e37d..46c329c85 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -139,7 +139,7 @@ def execute(*cmd, **kwargs): :attempts How many times to retry cmd. :run_as_root True | False. Defaults to False. If set to True, the command is prefixed by the command specified - in the sudo_helper FLAG. + in the root_helper FLAG. :raises exception.Error on receiving unknown arguments :raises exception.ProcessExecutionError @@ -155,7 +155,7 @@ def execute(*cmd, **kwargs): 'to utils.execute: %r') % kwargs) if run_as_root: - cmd = shlex.split(FLAGS.sudo_helper) + list(cmd) + cmd = shlex.split(FLAGS.root_helper) + list(cmd) cmd = map(str, cmd) while attempts > 0: -- cgit From d5bcc6764b418d7aa16b53b50173261385445ee8 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 11:31:48 -0700 Subject: initial port --- nova/api/openstack/contrib/keypairs.py | 137 +++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 nova/api/openstack/contrib/keypairs.py (limited to 'nova') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py new file mode 100644 index 000000000..879bd9712 --- /dev/null +++ b/nova/api/openstack/contrib/keypairs.py @@ -0,0 +1,137 @@ +# 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. + +""" Keypair management extension""" + +from webob import exc + +from nova import db +from nova import crypto +from nova import exception +from nova.api.openstack import extensions + + +class KeypairController(object): + """ Keypair API controller for the Openstack API """ + + # TODO(ja): the keypair crud logic should be in nova.compute.API? + + def _gen_key(self): + """ + Generate a key + """ + # TODO(ja): crypto.generate_key_pair is currently a slow method + # and should probably be moved to a process pool? + private_key, public_key, fingerprint = crypto.generate_key_pair() + return {'private_key': private_key, + 'public_key': public_key, + 'fingerprint': fingerprint} + + def create(self, req, body): + """ + Create or import keypair. + + Sending key_name will generate a key and return private_key + and fingerprint. + + You can send a public_key to add an existing ssh key + + params: keypair object with: + key_name (required) - string + fingerprint (optional) - string + public_key (optional) - string + """ + + context = req.environ['nova.context'] + params = body['keypair'] + key_name = params['key_name'] + + # NOTE(ja): generation is slow, so shortcut invalid key_name exception + try: + db.key_pair_get(context, context.user_id, key_name) + raise exception.KeyPairExists(key_name=key_name) + except exception.NotFound: + pass + + keypair = {'user_id': context.user_id, + 'name': key_name} + + # import if public_key is sent + if 'public_key' in params: + keypair['public_key'] = params['public_key'] + keypair['fingerprint'] = params.get('fingerprint', None) + else: + generated_key = self._gen_key() + keypair['private_key'] = generated_key['private_key'] + keypair['public_key'] = generated_key['public_key'] + keypair['fingerprint'] = generated_key['fingerprint'] + + db.key_pair_create(context, keypair) + return {'keypair': keypair} + + def delete(self, req, name): + """ + Delete a keypair with a given name + """ + context = req.environ['nova.context'] + db.key_pair_destroy(context, context.user_id, name) + return exc.HTTPAccepted() + + def index(self, req): + """ + List of keypairs for a user + """ + context = req.environ['nova.context'] + key_pairs = db.key_pair_get_all_by_user(context, context.user_id) + rval = [] + for key_pair in key_pairs: + rval.append({ + 'name': key_pair['name'], + 'key_name': key_pair['name'], + 'fingerprint': key_pair['fingerprint'], + }) + + return {'keypairs': rval} + + +class Keypairs(extensions.ExtensionDescriptor): + + def get_name(self): + return "Keypairs" + + def get_alias(self): + return "os-keypairs" + + def get_description(self): + return "Keypair Support" + + def get_namespace(self): + return \ + "http://docs.openstack.org/ext/keypairs/api/v1.1" + + def get_updated(self): + return "2011-08-08T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension( + 'os-keypairs', + KeypairController()) + + resources.append(res) + return resources -- cgit From 057b6ad650013a952f88f6e02f3e3db0164084d1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 13:00:13 -0700 Subject: added tests - list doesn't pass due to unicode issues --- nova/api/openstack/contrib/keypairs.py | 26 +++--- nova/crypto.py | 10 ++- nova/tests/api/openstack/contrib/test_keypairs.py | 99 +++++++++++++++++++++++ 3 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 nova/tests/api/openstack/contrib/test_keypairs.py (limited to 'nova') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index 879bd9712..8435494e1 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -23,19 +23,20 @@ from nova import db from nova import crypto from nova import exception from nova.api.openstack import extensions - +import os +import shutil +import tempfile class KeypairController(object): """ Keypair API controller for the Openstack API """ - # TODO(ja): the keypair crud logic should be in nova.compute.API? + # TODO(ja): both this file and nova.api.ec2.cloud.py have similar logic. + # move the common keypair logic to nova.compute.API? def _gen_key(self): """ Generate a key """ - # TODO(ja): crypto.generate_key_pair is currently a slow method - # and should probably be moved to a process pool? private_key, public_key, fingerprint = crypto.generate_key_pair() return {'private_key': private_key, 'public_key': public_key, @@ -52,7 +53,6 @@ class KeypairController(object): params: keypair object with: key_name (required) - string - fingerprint (optional) - string public_key (optional) - string """ @@ -72,8 +72,14 @@ class KeypairController(object): # import if public_key is sent if 'public_key' in params: + tmpdir = tempfile.mkdtemp() + fn = os.path.join(tmpdir, 'import.pub') + with open(fn, 'w') as pub: + pub.write(params['public_key']) + fingerprint = crypto.generate_fingerprint(fn) + shutil.rmtree(tmpdir) keypair['public_key'] = params['public_key'] - keypair['fingerprint'] = params.get('fingerprint', None) + keypair['fingerprint'] = fingerprint else: generated_key = self._gen_key() keypair['private_key'] = generated_key['private_key'] @@ -83,12 +89,12 @@ class KeypairController(object): db.key_pair_create(context, keypair) return {'keypair': keypair} - def delete(self, req, name): + def delete(self, req, id): """ Delete a keypair with a given name """ context = req.environ['nova.context'] - db.key_pair_destroy(context, context.user_id, name) + db.key_pair_destroy(context, context.user_id, id) return exc.HTTPAccepted() def index(self, req): @@ -99,11 +105,11 @@ class KeypairController(object): key_pairs = db.key_pair_get_all_by_user(context, context.user_id) rval = [] for key_pair in key_pairs: - rval.append({ + rval.append({'keypair': { 'name': key_pair['name'], 'key_name': key_pair['name'], 'fingerprint': key_pair['fingerprint'], - }) + }}) return {'keypairs': rval} diff --git a/nova/crypto.py b/nova/crypto.py index 8d535f426..71bef80f2 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -104,6 +104,12 @@ def fetch_ca(project_id=None, chain=True): return buffer +def generate_fingerprint(public_key): + (out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', public_key) + fingerprint = out.split(' ')[1] + return fingerprint + + def generate_key_pair(bits=1024): # what is the magic 65537? @@ -111,9 +117,7 @@ def generate_key_pair(bits=1024): keyfile = os.path.join(tmpdir, 'temp') utils.execute('ssh-keygen', '-q', '-b', bits, '-N', '', '-f', keyfile) - (out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', - '%s.pub' % (keyfile)) - fingerprint = out.split(' ')[1] + fingerprint = generate_fingerprint('%s.pub' % (keyfile)) private_key = open(keyfile).read() public_key = open(keyfile + '.pub').read() diff --git a/nova/tests/api/openstack/contrib/test_keypairs.py b/nova/tests/api/openstack/contrib/test_keypairs.py new file mode 100644 index 000000000..1f6028e08 --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_keypairs.py @@ -0,0 +1,99 @@ +# Copyright 2011 Eldar Nugaev +# 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 json +import webob + +from nova import context +from nova import db +from nova import test +from nova.api.openstack.contrib.keypairs import KeypairController +from nova.tests.api.openstack import fakes + + +def fake_keypair(name): + return {'public_key': 'FAKE_KEY', + 'fingerprint': 'FAKE_FINGERPRINT', + 'name': name} + +def db_key_pair_get_all_by_user(self, user_id): + return [fake_keypair('FAKE')] + + +def db_key_pair_create(self, keypair): + pass + + +def db_key_pair_destroy(context, user_id, key_name): + if not (user_id and key_name): + raise Exception() + + +class KeypairsTest(test.TestCase): + + def setUp(self): + super(KeypairsTest, self).setUp() + self.controller = KeypairController() + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + self.stubs.Set(db, "key_pair_get_all_by_user", + db_key_pair_get_all_by_user) + self.stubs.Set(db, "key_pair_create", + db_key_pair_create) + self.stubs.Set(db, "key_pair_destroy", + db_key_pair_destroy) + self.context = context.get_admin_context() + + def test_keypair_list(self): + req = webob.Request.blank('/v1.1/os-keypairs') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + response = {'keypairs': [{'keypair': fake_keypair('FAKE')}]} + self.assertEqual(res_dict, response) + + def test_keypair_create(self): + body = {'keypair': {'key_name': 'create_test'}} + req = webob.Request.blank('/v1.1/os-keypairs') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['Content-Type'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertTrue(len(res_dict['keypair']['fingerprint']) > 0) + self.assertTrue(len(res_dict['keypair']['private_key']) > 0) + + def test_keypair_import(self): + body = {'keypair': {'key_name': 'create_test', + 'public_key': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBYIznAx9D7118Q1VKGpXy2HDiKyUTM8XcUuhQpo0srqb9rboUp4a9NmCwpWpeElDLuva707GOUnfaBAvHBwsRXyxHJjRaI6YQj2oLJwqvaSaWUbyT1vtryRqy6J3TecN0WINY71f4uymiMZP0wby4bKBcYnac8KiCIlvkEl0ETjkOGUq8OyWRmn7ljj5SESEUdBP0JnuTFKddWTU/wD6wydeJaUhBTqOlHn0kX1GyqoNTE1UEhcM5ZRWgfUZfTjVyDF2kGj3vJLCJtJ8LoGcj7YaN4uPg1rBle+izwE/tLonRrds+cev8p6krSSrxWOwBbHkXa6OciiJDvkRzJXzf'}} + req = webob.Request.blank('/v1.1/os-keypairs') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['Content-Type'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + # FIXME(ja): sholud we check that public_key was sent to create? + res_dict = json.loads(res.body) + self.assertTrue(len(res_dict['keypair']['fingerprint']) > 0) + self.assertFalse('private_key' in res_dict['keypair']) + + def test_keypair_delete(self): + req = webob.Request.blank('/v1.1/os-keypairs/FAKE') + req.method = 'DELETE' + req.headers['Content-Type'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + -- cgit From 568188c158db3caddfcd9ecb384189f93b076dd9 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 14:23:30 -0700 Subject: add Keypairs to test_extensions --- nova/tests/api/openstack/test_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 409fa0e71..eb9afbdb1 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -96,7 +96,7 @@ class ExtensionControllerTest(test.TestCase): names = [x['name'] for x in data['extensions']] names.sort() self.assertEqual(names, ["FlavorExtraSpecs", "Floating_ips", - "Fox In Socks", "Hosts", "Multinic", "Volumes"]) + "Fox In Socks", "Hosts", "Keypairs", "Multinic", "Volumes"]) # Make sure that at least Fox in Sox is correct. (fox_ext,) = [ @@ -143,7 +143,7 @@ class ExtensionControllerTest(test.TestCase): # Make sure we have all the extensions. exts = root.findall('{0}extension'.format(NS)) - self.assertEqual(len(exts), 6) + self.assertEqual(len(exts), 7) # Make sure that at least Fox in Sox is correct. (fox_ext,) = [x for x in exts if x.get('alias') == 'FOXNSOX'] -- cgit From 131b2185e23567895e3f87cdfe9c2822d18910b2 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 16:12:59 -0700 Subject: tests pass --- nova/api/openstack/contrib/keypairs.py | 16 ++++++++-------- nova/tests/api/openstack/contrib/test_keypairs.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index 8435494e1..d718846f1 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -46,29 +46,29 @@ class KeypairController(object): """ Create or import keypair. - Sending key_name will generate a key and return private_key + Sending name will generate a key and return private_key and fingerprint. You can send a public_key to add an existing ssh key params: keypair object with: - key_name (required) - string + name (required) - string public_key (optional) - string """ context = req.environ['nova.context'] params = body['keypair'] - key_name = params['key_name'] + name = params['name'] - # NOTE(ja): generation is slow, so shortcut invalid key_name exception + # NOTE(ja): generation is slow, so shortcut invalid name exception try: - db.key_pair_get(context, context.user_id, key_name) - raise exception.KeyPairExists(key_name=key_name) + db.key_pair_get(context, context.user_id, name) + raise exception.KeyPairExists(key_name=name) except exception.NotFound: pass keypair = {'user_id': context.user_id, - 'name': key_name} + 'name': name} # import if public_key is sent if 'public_key' in params: @@ -107,7 +107,7 @@ class KeypairController(object): for key_pair in key_pairs: rval.append({'keypair': { 'name': key_pair['name'], - 'key_name': key_pair['name'], + 'public_key': key_pair['public_key'], 'fingerprint': key_pair['fingerprint'], }}) diff --git a/nova/tests/api/openstack/contrib/test_keypairs.py b/nova/tests/api/openstack/contrib/test_keypairs.py index 1f6028e08..c9dc34d65 100644 --- a/nova/tests/api/openstack/contrib/test_keypairs.py +++ b/nova/tests/api/openstack/contrib/test_keypairs.py @@ -36,8 +36,8 @@ def db_key_pair_create(self, keypair): pass -def db_key_pair_destroy(context, user_id, key_name): - if not (user_id and key_name): +def db_key_pair_destroy(context, user_id, name): + if not (user_id and name): raise Exception() @@ -65,7 +65,7 @@ class KeypairsTest(test.TestCase): self.assertEqual(res_dict, response) def test_keypair_create(self): - body = {'keypair': {'key_name': 'create_test'}} + body = {'keypair': {'name': 'create_test'}} req = webob.Request.blank('/v1.1/os-keypairs') req.method = 'POST' req.body = json.dumps(body) @@ -77,7 +77,7 @@ class KeypairsTest(test.TestCase): self.assertTrue(len(res_dict['keypair']['private_key']) > 0) def test_keypair_import(self): - body = {'keypair': {'key_name': 'create_test', + body = {'keypair': {'name': 'create_test', 'public_key': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBYIznAx9D7118Q1VKGpXy2HDiKyUTM8XcUuhQpo0srqb9rboUp4a9NmCwpWpeElDLuva707GOUnfaBAvHBwsRXyxHJjRaI6YQj2oLJwqvaSaWUbyT1vtryRqy6J3TecN0WINY71f4uymiMZP0wby4bKBcYnac8KiCIlvkEl0ETjkOGUq8OyWRmn7ljj5SESEUdBP0JnuTFKddWTU/wD6wydeJaUhBTqOlHn0kX1GyqoNTE1UEhcM5ZRWgfUZfTjVyDF2kGj3vJLCJtJ8LoGcj7YaN4uPg1rBle+izwE/tLonRrds+cev8p6krSSrxWOwBbHkXa6OciiJDvkRzJXzf'}} req = webob.Request.blank('/v1.1/os-keypairs') req.method = 'POST' -- cgit From 8c6ebaf9861e3b652663c0ff60ad545864f72677 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 9 Aug 2011 19:34:51 -0400 Subject: use correct variable name --- nova/db/sqlalchemy/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 1da54b3da..8119cdfb8 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3346,8 +3346,8 @@ def instance_metadata_update(context, instance_id, metadata, delete): item = {"value": meta_value} try: - meta_ref = instance_metadata_get_item( - context, instance_id, key, session) + meta_ref = instance_metadata_get_item(context, instance_id, + meta_key, session) except exception.InstanceMetadataNotFound, e: meta_ref = models.InstanceMetadata() item.update({"key": meta_key, "instance_id": instance_id}) -- cgit From 83ed9faa488b3ec0f1cb16e7147293c912e2fc2b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 9 Aug 2011 19:35:40 -0400 Subject: pep8 fix --- nova/api/openstack/contrib/floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 52c9c6cf9..2aba1068a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,7 +102,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) - + if 'fixed_ip' in ip: try: self.disassociate(req, id, '') -- cgit From 0719b038ba1548f98668991507c001eb18c82981 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 17:22:01 -0700 Subject: import formatting - thx --- nova/api/openstack/contrib/keypairs.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index d718846f1..201648ab5 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -17,15 +17,17 @@ """ Keypair management extension""" +import os +import shutil +import tempfile + from webob import exc -from nova import db from nova import crypto +from nova import db from nova import exception from nova.api.openstack import extensions -import os -import shutil -import tempfile + class KeypairController(object): """ Keypair API controller for the Openstack API """ -- cgit From b3b13df03f0e843300f63bded410ffc4e0bd4e9f Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 10 Aug 2011 11:25:38 -0500 Subject: Removed verbose debugging output when capabilities are reported. --- nova/scheduler/zone_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index 97bdf3d44..9d05ea42e 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -198,7 +198,7 @@ class ZoneManager(object): def update_service_capabilities(self, service_name, host, capabilities): """Update the per-service capabilities based on this notification.""" logging.debug(_("Received %(service_name)s service update from " - "%(host)s: %(capabilities)s") % locals()) + "%(host)s.") % locals()) service_caps = self.service_states.get(host, {}) capabilities["timestamp"] = utils.utcnow() # Reported time service_caps[service_name] = capabilities -- cgit From 7d9ff45ec1acc3cba0d58341f3cce011df140f8e Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Thu, 11 Aug 2011 12:37:29 +0100 Subject: Remove doublequotes from env variable setting since they are literally passed --- nova/network/linux_net.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 8b7b59f55..4e1e1f85a 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -616,8 +616,8 @@ def update_dhcp(context, network_ref): else: LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid) - cmd = ['FLAGFILE="%s"' % FLAGS.dhcpbridge_flagfile, - 'DNSMASQ_INTERFACE="%s"' % network_ref['bridge'], + cmd = ['FLAGFILE=%s' % FLAGS.dhcpbridge_flagfile, + 'DNSMASQ_INTERFACE=%s' % network_ref['bridge'], 'dnsmasq', '--strict-order', '--bind-interfaces', -- cgit