diff --git a/nova/compute/api.py b/nova/compute/api.py index 5862b6d452f9..4d0654d20aa7 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'] - # Convert any responses into a list of instances - if instances is None: - instances = [] - elif not isinstance(instances, list): - instances = [instances] + recurse_zones = filters.pop('recurse_zones', False) + if 'reservation_id' in filters: + recurse_zones = True - if not search_opts.get('recurse_zones', False): + instances = self.db.instance_get_all_by_filters(context, filters) + + 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 22dd8a4b4614..c7d5420e1a92 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 e4250a6cb9b1..25a486b9ce65 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()