From 1f99e500a99a4d66639f04f2c723058c4d1dfc1d Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 22 Jun 2011 13:45:24 -0700 Subject: Check API request for min_count/max_count for number of instances to build --- nova/api/openstack/create_instance_helper.py | 6 ++++++ nova/api/openstack/servers.py | 9 +++++---- nova/compute/api.py | 10 +++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 436e524c1..3e055936c 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -114,6 +114,12 @@ class CreateInstanceHelper(object): name = name.strip() reservation_id = body['server'].get('reservation_id') + min_count = body['server'].get('min_count') + max_count = body['server'].get('max_count') + if min_count: + min_count = int(min_count) + if max_count: + max_count = int(max_count) try: inst_type = \ diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b82a6de19..31ec46e8e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -111,14 +111,15 @@ class Controller(object): extra_values = None result = None try: - extra_values, result = self.helper.create_instance( + extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) except faults.Fault, f: return f - instances = result - - (inst, ) = instances + # We can only return 1 instance via the API, if we happen to + # build more than one... instances is a list, so we'll just + # use the first one.. + inst = instances[0] for key in ['instance_type', 'image_ref']: inst[key] = extra_values[key] diff --git a/nova/compute/api.py b/nova/compute/api.py index a7ea88d51..44e11d187 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -143,7 +143,7 @@ class API(base.Base): def _check_create_parameters(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, + 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={}, @@ -154,6 +154,10 @@ class API(base.Base): if not instance_type: instance_type = instance_types.get_default_instance_type() + if not min_count: + min_count = 1 + if not max_count: + max_count = min_count num_instances = quota.allowed_instances(context, max_count, instance_type) @@ -338,7 +342,7 @@ class API(base.Base): def create_all_at_once(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, + 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={}, @@ -368,7 +372,7 @@ class API(base.Base): def create(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, + 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={}, -- cgit From b3c206594113ea6e9200e600490c6c991ca319d0 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 01:22:50 -0700 Subject: Add some resource checking for memory available when scheduling Various changes to d-sched to plan for scheduling on different topics, which cleans up some of the resource checking. Re-compute weights when building more than 1 instance, accounting for resources that would be consumed. --- nova/scheduler/host_filter.py | 10 ++-- nova/scheduler/least_cost.py | 39 +++++++++----- nova/scheduler/zone_aware_scheduler.py | 94 +++++++++++++++++++++++++++------- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index bd6b26608..818ae4a30 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -305,8 +305,11 @@ class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler): 'instance_type': } """ - def filter_hosts(self, num, request_spec): + def filter_hosts(self, topic, request_spec, hosts): """Filter the full host list (from the ZoneManager)""" + + if hosts: + return hosts filter_name = request_spec.get('filter', None) host_filter = choose_host_filter(filter_name) @@ -317,8 +320,9 @@ class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler): name, query = host_filter.instance_type_to_filter(instance_type) return host_filter.filter_hosts(self.zone_manager, query) - def weigh_hosts(self, num, request_spec, hosts): + def weigh_hosts(self, topic, request_spec, hosts): """Derived classes must override this method and return a lists of hosts in [{weight, hostname}] format. """ - return [dict(weight=1, hostname=host) for host, caps in hosts] + return [dict(weight=1, hostname=hostname, capabilities=caps) + for hostname, caps in hosts] diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 629fe2e42..72db2fd1b 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -48,25 +48,36 @@ def noop_cost_fn(host): return 1 -flags.DEFINE_integer('fill_first_cost_fn_weight', 1, +flags.DEFINE_integer('compute_fill_first_cost_fn_weight', 1, 'How much weight to give the fill-first cost function') -def fill_first_cost_fn(host): +def compute_fill_first_cost_fn(host): """Prefer hosts that have less ram available, filter_hosts will exclude hosts that don't have enough ram""" hostname, caps = host - free_mem = caps['compute']['host_memory_free'] + free_mem = caps['host_memory_free'] return free_mem class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): - def get_cost_fns(self): + def __init__(self, *args, **kwargs): + self.cost_fns_cache = {} + super(LeastCoastScheduler, self).__init__(*args, **kwargs) + + def get_cost_fns(self, topic): """Returns a list of tuples containing weights and cost functions to use for weighing hosts """ + + if topic in self.cost_fns_cache: + return self.cost_fns_cache[topic] + cost_fns = [] for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions: + if not cost_fn_str.startswith('%s_' % topic) and \ + not cost_fn_str.startswith('noop'): + continue try: # NOTE(sirp): import_class is somewhat misnamed since it can @@ -84,23 +95,23 @@ class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): cost_fns.append((weight, cost_fn)) + self.cost_fns_cache[topic] = cost_fns return cost_fns - def weigh_hosts(self, num, request_spec, hosts): + def weigh_hosts(self, topic, request_spec, hosts): """Returns a list of dictionaries of form: - [ {weight: weight, hostname: hostname} ]""" - - # FIXME(sirp): weigh_hosts should handle more than just instances - hostnames = [hostname for hostname, caps in hosts] + [ {weight: weight, hostname: hostname, capabilities: capabs} ] + """ - cost_fns = self.get_cost_fns() + cost_fns = self.get_cost_fns(topic) costs = weighted_sum(domain=hosts, weighted_fns=cost_fns) weighted = [] weight_log = [] - for cost, hostname in zip(costs, hostnames): + for cost, (hostname, caps) in zip(costs, hosts): weight_log.append("%s: %s" % (hostname, "%.2f" % cost)) - weight_dict = dict(weight=cost, hostname=hostname) + weight_dict = dict(weight=cost, hostname=hostname, + capabilities=caps) weighted.append(weight_dict) LOG.debug(_("Weighted Costs => %s") % weight_log) @@ -127,7 +138,8 @@ def weighted_sum(domain, weighted_fns, normalize=True): weighted_fns - list of weights and functions like: [(weight, objective-functions)] - Returns an unsorted of scores. To pair with hosts do: zip(scores, hosts) + Returns an unsorted list of scores. To pair with hosts do: + zip(scores, hosts) """ # Table of form: # { domain1: [score1, score2, ..., scoreM] @@ -150,7 +162,6 @@ def weighted_sum(domain, weighted_fns, normalize=True): domain_scores = [] for idx in sorted(score_table): elem_score = sum(score_table[idx]) - elem = domain[idx] domain_scores.append(elem_score) return domain_scores diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index e7bff2faa..d4d3d0414 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -224,18 +224,34 @@ class ZoneAwareScheduler(driver.Scheduler): raise NotImplemented(_("Zone Aware Scheduler only understands " "Compute nodes (for now)")) - #TODO(sandy): how to infer this from OS API params? - num_instances = 1 - - # Filter local hosts based on requirements ... - host_list = self.filter_hosts(num_instances, request_spec) + num_instances = request_spec['num_instances'] + instance_type = request_spec['instance_type'] - # TODO(sirp): weigh_hosts should also be a function of 'topic' or - # resources, so that we can apply different objective functions to it + weighted = [] + host_list = None + + for i in xrange(num_instances): + # Filter local hosts based on requirements ... + # + # The first pass through here will pass 'None' as the + # host_list.. which tells the filter to build the full + # list of hosts. + # On a 2nd pass, the filter can modify the host_list with + # any updates it needs to make based on resources that + # may have been consumed from a previous build.. + host_list = self.filter_hosts(topic, request_spec, host_list) + if not host_list: + break - # then weigh the selected hosts. - # weighted = [{weight=weight, name=hostname}, ...] - weighted = self.weigh_hosts(num_instances, request_spec, host_list) + # then weigh the selected hosts. + # weighted = [{weight=weight, hostname=hostname, + # capabilities=capabs}, ...] + weights = self.weigh_hosts(topic, request_spec, host_list) + weights.sort(key=operator.itemgetter('weight')) + best_weight = weights[0] + weighted.append(best_weight) + self.consume_resources(best_weight['capabilities'], + instance_type) # Next, tack on the best weights from the child zones ... json_spec = json.dumps(request_spec) @@ -254,18 +270,58 @@ class ZoneAwareScheduler(driver.Scheduler): weighted.sort(key=operator.itemgetter('weight')) return weighted - def filter_hosts(self, num, request_spec): - """Derived classes must override this method and return - a list of hosts in [(hostname, capability_dict)] format. + def compute_filter(self, hostname, capabilities, request_spec): + """Return whether or not we can schedule to this compute node. + Derived classes should override this and return True if the host + is acceptable for scheduling. """ - # NOTE(sirp): The default logic is the equivalent to AllHostsFilter - service_states = self.zone_manager.service_states - return [(host, services) - for host, services in service_states.iteritems()] + instance_type = request_spec['instance_type'] + reqested_mem = instance_type['memory_mb'] + return capabilities['host_memory_free'] >= requested_mem + + def filter_hosts(self, topic, request_spec, host_list=None): + """Return a list of hosts which are acceptable for scheduling. + Return value should be a list of (hostname, capability_dict)s. + Derived classes may override this, but may find the + '_filter' function more appropriate. + """ + + def _default_filter(self, hostname, capabilities, request_spec): + """Default filter function if there's no _filter""" + # NOTE(sirp): The default logic is the equivalent to + # AllHostsFilter + return True + + filter_func = getattr(self, '%s_filter' % topic, _default_filter) - def weigh_hosts(self, num, request_spec, hosts): + filtered_hosts = [] + if host_list is None: + host_list = self.zone_manager.service_states.iteritems() + for host, services in host_list: + if topic not in services: + continue + if filter_func(host, services['topic'], request_spec): + filtered_hosts.append((host, services['topic'])) + + def weigh_hosts(self, topic, request_spec, hosts): """Derived classes may override this to provide more sophisticated scheduling objectives """ # NOTE(sirp): The default logic is the same as the NoopCostFunction - return [dict(weight=1, hostname=host) for host, caps in hosts] + return [dict(weight=1, hostname=hostname, capabilities=capabilities) + for hostname, capabilities in hosts] + + def compute_consume(self, capabilities, instance_type): + """Consume compute resources for selected host""" + + requested_mem = max(instance_type['memory_mb'], 0) + capabilities['host_memory_free'] -= requested_mem + + def consume_resources(self, topic, capabilities, instance_type): + """Consume resources for a specific host. 'host' is a tuple + of the hostname and the services""" + + consume_func = getattr(self, '%s_consume' % topic, None) + if not consume_func: + return + consume_func(capabilities, instance_type) -- cgit From b9a861d72f1a98510dd4b68e547b434388ab9a64 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 05:20:50 -0700 Subject: add support for compute_api.get_all() recursing zones for more than just reservation_id --- nova/api/openstack/servers.py | 13 +++++++-- nova/compute/api.py | 66 ++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b82a6de19..8c93ce230 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -76,10 +76,19 @@ class Controller(object): builder - the response model builder """ - reservation_id = req.str_GET.get('reservation_id') + 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 = query_str.get('recurse_zones') + if recurse_zones is not None: + recurse_zones = True instance_list = self.compute_api.get_all( req.environ['nova.context'], - reservation_id=reservation_id) + reservation_id=reservation_id, + project_id=project_id, + fixed_ip=fixed_ip, + recurse_zones=recurse_zones) 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 a7ea88d51..9db83d65f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -603,50 +603,52 @@ class API(base.Base): """ return self.get(context, instance_id) - def get_all_across_zones(self, context, reservation_id): - """Get all instances with this reservation_id, across - all available Zones (if any). - """ - context = context.elevated() - instances = self.db.instance_get_all_by_reservation( - context, reservation_id) - - children = scheduler_api.call_zone_method(context, "list", - novaclient_collection_name="servers", - reservation_id=reservation_id) - - for zone, servers in children: - for server in servers: - # Results are ready to send to user. No need to scrub. - server._info['_is_precooked'] = True - instances.append(server._info) - return instances - def get_all(self, context, project_id=None, reservation_id=None, - fixed_ip=None): + fixed_ip=None, recurse_zones=False): """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: - return self.get_all_across_zones(context, reservation_id) + admin_context = context.elevated() - if fixed_ip is not None: - return self.db.fixed_ip_get_instance(context, fixed_ip) - - if project_id or not context.is_admin: + 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: + instances = self.db.fixed_ip_get_instance(context, fixed_ip) + elif project_id or not context.is_admin: if not context.project: - return self.db.instance_get_all_by_user( + 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) + + if not isinstance(instances, list): + instances = [instances] - if project_id is None: - project_id = context.project_id + if not recurse_zones: + return instances - return self.db.instance_get_all_by_project( - context, project_id) + 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) - return self.db.instance_get_all(context) + for zone, servers in children: + for server in servers: + # Results are ready to send to user. No need to scrub. + server._info['_is_precooked'] = True + instances.append(server._info) + return instances def _cast_compute_message(self, method, context, instance_id, host=None, params=None): -- cgit From 1aa7e746d5918f2a664da1937183b66fe31f6bd4 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 05:35:04 -0700 Subject: typo --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 9db83d65f..1b3997db7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -640,7 +640,7 @@ class API(base.Base): novaclient_collection_name="servers", reservation_id=reservation_id, project_id=project_id, - fixed_ip=fixed_ip + fixed_ip=fixed_ip, recurse_zones=True) for zone, servers in children: -- cgit From 07404e266a4a6b690c62624a9a5e47d60cab7d5b Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 06:33:25 -0700 Subject: fixes for recurse_zones and None instances with compute's get_all --- nova/api/openstack/servers.py | 3 +-- nova/compute/api.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8c93ce230..9e861359a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -81,8 +81,7 @@ class Controller(object): project_id = query_str.get('project_id') fixed_ip = query_str.get('fixed_ip') recurse_zones = query_str.get('recurse_zones') - if recurse_zones is not None: - recurse_zones = True + recurse_zones = recurse_zones and True or False instance_list = self.compute_api.get_all( req.environ['nova.context'], reservation_id=reservation_id, diff --git a/nova/compute/api.py b/nova/compute/api.py index 1b3997db7..e31edf7bb 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -610,7 +610,6 @@ class API(base.Base): If there is no filter and the context is an admin, it will retreive all instances in the system. """ - admin_context = context.elevated() if reservation_id is not None: recurse_zones = True @@ -630,12 +629,15 @@ class API(base.Base): else: instances = self.db.instance_get_all(context) - if not isinstance(instances, list): + if instances is None: + instances = [] + elif not isinstance(instances, list): instances = [instances] if not recurse_zones: return instances + admin_context = context.elevated() children = scheduler_api.call_zone_method(admin_context, "list", novaclient_collection_name="servers", reservation_id=reservation_id, -- cgit From 575ea1963bef8c76597ef3a6541c5d0c13635b17 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 06:52:19 -0700 Subject: minor fixups --- nova/scheduler/host_filter.py | 4 +--- nova/scheduler/zone_aware_scheduler.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 818ae4a30..b336665be 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -305,11 +305,9 @@ class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler): 'instance_type': } """ - def filter_hosts(self, topic, request_spec, hosts): + def filter_hosts(self, topic, request_spec, hosts=None): """Filter the full host list (from the ZoneManager)""" - if hosts: - return hosts filter_name = request_spec.get('filter', None) host_filter = choose_host_filter(filter_name) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index d4d3d0414..70cb83e8a 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -302,6 +302,7 @@ class ZoneAwareScheduler(driver.Scheduler): continue if filter_func(host, services['topic'], request_spec): filtered_hosts.append((host, services['topic'])) + return filtered_hosts def weigh_hosts(self, topic, request_spec, hosts): """Derived classes may override this to provide more sophisticated -- cgit From e241f5301621e66360bb884193884f9f98bc8832 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 07:02:49 -0700 Subject: str_GET is a property --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9e861359a..d499066be 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -76,7 +76,7 @@ class Controller(object): builder - the response model builder """ - query_str = req.str_GET() + 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') -- cgit From b637dee5a5c48f86f6b8b12b3b374344b4ffc5b7 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 07:15:20 -0700 Subject: handle errors for listing an instance by IP address --- nova/compute/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 02963068a..31333ad18 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -620,7 +620,12 @@ class API(base.Base): instances = self.db.instance_get_all_by_reservation( context, reservation_id) elif fixed_ip is not None: - instances = self.db.fixed_ip_get_instance(context, fixed_ip) + try: + instances = self.db.fixed_ip_get_instance(context, fixed_ip) + except exception.FloatingIpNotFound, e: + if not recurse_zones: + raise + instances = None elif project_id or not context.is_admin: if not context.project: instances = self.db.instance_get_all_by_user( -- cgit From 16b858d804c3df473617c776a7cb74ea284b8f3a Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 09:43:27 -0700 Subject: an int() was missed being removed from UUID changes when zone rerouting kicks in --- nova/scheduler/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 1bb047e2e..0aed75680 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -169,7 +169,7 @@ def _issue_novaclient_command(nova, zone, collection, method_name, item_id): result = None try: try: - result = manager.get(int(item_id)) + result = manager.get(item_id) except ValueError, e: result = manager.find(name=item_id) except novaclient.NotFound: -- cgit From 65ec0ce423e211215d82001778560dcaa92866a1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 21:59:54 -0700 Subject: missed passing an argument to consume_resources --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 70cb83e8a..073bdd3bd 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -250,7 +250,7 @@ class ZoneAwareScheduler(driver.Scheduler): weights.sort(key=operator.itemgetter('weight')) best_weight = weights[0] weighted.append(best_weight) - self.consume_resources(best_weight['capabilities'], + self.consume_resources(topic, best_weight['capabilities'], instance_type) # Next, tack on the best weights from the child zones ... -- cgit From 2c303bcc8f47aaa5cdeee0ee91e3f4b434176f15 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 22:45:58 -0700 Subject: missed passing in min/max_count into the create/create_all_at_once calls --- nova/api/openstack/create_instance_helper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 3e055936c..4c6cc0f83 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -120,6 +120,8 @@ class CreateInstanceHelper(object): min_count = int(min_count) if max_count: max_count = int(max_count) + if min_count > max_count: + min_count = max_count try: inst_type = \ @@ -143,7 +145,9 @@ class CreateInstanceHelper(object): injected_files=injected_files, admin_password=password, zone_blob=zone_blob, - reservation_id=reservation_id)) + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: -- cgit From 72d478b3ac12033928a53d51aa9c0ffbdfc9907f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 22:48:44 -0700 Subject: debug logging of number of instances to build in scheduler --- nova/scheduler/zone_aware_scheduler.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 073bdd3bd..a747526d1 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -180,18 +180,21 @@ class ZoneAwareScheduler(driver.Scheduler): request_spec, kwargs) return None + num_instances = request_spec['num_instances'] + LOG.debug(_("Attemping to build %d instance%s") % + (num_instances, "" if num_instances == 1 else "s")) + # Create build plan and provision ... build_plan = self.select(context, request_spec) if not build_plan: raise driver.NoValidHost(_('No hosts were available')) - for num in xrange(request_spec['num_instances']): + for num in xrange(num_instances): if not build_plan: break - item = build_plan.pop(0) - self._provision_resource(context, item, instance_id, request_spec, - kwargs) + self._provision_resource(context, item, instance_id, + request_spec, kwargs) # Returning None short-circuits the routing to Compute (since # we've already done it here) -- cgit From 9ededda0bdc990a4e6823f5076aa8b9e2de43c7e Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 22:55:45 -0700 Subject: typo in least cost scheduler --- nova/scheduler/least_cost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 72db2fd1b..9376631ef 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -63,7 +63,7 @@ def compute_fill_first_cost_fn(host): class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): def __init__(self, *args, **kwargs): self.cost_fns_cache = {} - super(LeastCoastScheduler, self).__init__(*args, **kwargs) + super(LeastCostScheduler, self).__init__(*args, **kwargs) def get_cost_fns(self, topic): """Returns a list of tuples containing weights and cost functions to -- cgit From 56bbeaa4881979af281ded41b897ad87697f331a Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 23:00:15 -0700 Subject: more typos --- nova/scheduler/zone_aware_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index a747526d1..8f218c159 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -303,8 +303,8 @@ class ZoneAwareScheduler(driver.Scheduler): for host, services in host_list: if topic not in services: continue - if filter_func(host, services['topic'], request_spec): - filtered_hosts.append((host, services['topic'])) + if filter_func(host, services[topic], request_spec): + filtered_hosts.append((host, services[topic])) return filtered_hosts def weigh_hosts(self, topic, request_spec, hosts): -- cgit From 108314eac2f3c91be68c525902ce31e3abab4ecd Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 23:05:12 -0700 Subject: requested_mem typo --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 8f218c159..769b2dd0f 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -279,7 +279,7 @@ class ZoneAwareScheduler(driver.Scheduler): is acceptable for scheduling. """ instance_type = request_spec['instance_type'] - reqested_mem = instance_type['memory_mb'] + requested_mem = instance_type['memory_mb'] return capabilities['host_memory_free'] >= requested_mem def filter_hosts(self, topic, request_spec, host_list=None): -- cgit From 4307daa9848060aad4b714394f314e5b6e823208 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 23 Jun 2011 23:38:32 -0700 Subject: LeastCostScheduler wasn't checking for topic cost functions correctly. Added support so that --least_cost_scheduler_cost_functions only needs to have method names specified, instead of the full blown version with module and class name. Still works the old way, too. --- nova/scheduler/least_cost.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 9376631ef..6f5eb66fd 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -75,8 +75,15 @@ class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): cost_fns = [] for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions: - if not cost_fn_str.startswith('%s_' % topic) and \ - not cost_fn_str.startswith('noop'): + if '.' in cost_fn_str: + short_name = cost_fn_str.split('.')[-1] + else: + short_name = cost_fn_str + cost_fn_str = "%s.%s.%s" % ( + __name__, self.__class__.__name__, short_name) + + if not (short_name.startswith('%s_' % topic) or + short_name.startswith('noop')): continue try: -- cgit From 95b9a83473dad5a2c60e146c0428b2c16d234232 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 24 Jun 2011 00:26:55 -0700 Subject: on 2nd run through filter_hosts, we've already accounted for the topic memory needs converted to Bytes from MB --- nova/scheduler/zone_aware_scheduler.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 769b2dd0f..e6383b20b 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -244,6 +244,8 @@ class ZoneAwareScheduler(driver.Scheduler): # may have been consumed from a previous build.. host_list = self.filter_hosts(topic, request_spec, host_list) if not host_list: + LOG.warn(_("Ran out of available hosts after weighing " + "%d of %d instances") % (i, num_instances)) break # then weigh the selected hosts. @@ -279,7 +281,7 @@ class ZoneAwareScheduler(driver.Scheduler): is acceptable for scheduling. """ instance_type = request_spec['instance_type'] - requested_mem = instance_type['memory_mb'] + requested_mem = instance_type['memory_mb'] * 1024 * 1024 return capabilities['host_memory_free'] >= requested_mem def filter_hosts(self, topic, request_spec, host_list=None): @@ -297,14 +299,20 @@ class ZoneAwareScheduler(driver.Scheduler): filter_func = getattr(self, '%s_filter' % topic, _default_filter) - filtered_hosts = [] if host_list is None: + first_run = True host_list = self.zone_manager.service_states.iteritems() + else: + first_run = False + + filtered_hosts = [] for host, services in host_list: - if topic not in services: - continue - if filter_func(host, services[topic], request_spec): - filtered_hosts.append((host, services[topic])) + if first_run: + if topic not in services: + continue + services = services['topic'] + if filter_func(host, services, request_spec): + filtered_hosts.append((host, services)) return filtered_hosts def weigh_hosts(self, topic, request_spec, hosts): @@ -318,7 +326,7 @@ class ZoneAwareScheduler(driver.Scheduler): def compute_consume(self, capabilities, instance_type): """Consume compute resources for selected host""" - requested_mem = max(instance_type['memory_mb'], 0) + requested_mem = max(instance_type['memory_mb'], 0) * 1024 * 1024 capabilities['host_memory_free'] -= requested_mem def consume_resources(self, topic, capabilities, instance_type): -- cgit From d97120f40b68870bebec0c41b13784a6cd1ddd92 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 24 Jun 2011 00:30:58 -0700 Subject: same typo i made before! --- nova/scheduler/zone_aware_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index e6383b20b..e24c2256f 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -310,7 +310,7 @@ class ZoneAwareScheduler(driver.Scheduler): if first_run: if topic not in services: continue - services = services['topic'] + services = services[topic] if filter_func(host, services, request_spec): filtered_hosts.append((host, services)) return filtered_hosts -- cgit From dc90a10e399310c5a2781970874ea0e747f62670 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 27 Jun 2011 15:05:37 -0700 Subject: Made _issue_novaclient_command() behave better. Fixed a bunch of tests. --- nova/scheduler/api.py | 49 ++++++++++++++++------- nova/scheduler/zone_aware_scheduler.py | 4 +- nova/tests/scheduler/test_least_cost_scheduler.py | 11 ++--- nova/tests/scheduler/test_scheduler.py | 4 +- nova/tests/scheduler/test_zone_aware_scheduler.py | 35 +++++++--------- 5 files changed, 60 insertions(+), 43 deletions(-) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 0aed75680..5d62beb86 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -162,32 +162,53 @@ def child_zone_helper(zone_list, func): _wrap_method(_process, func), zone_list)] -def _issue_novaclient_command(nova, zone, collection, method_name, item_id): +def _issue_novaclient_command(nova, zone, collection, + method_name, *args, **kwargs): """Use novaclient to issue command to a single child zone. - One of these will be run in parallel for each child zone.""" + One of these will be run in parallel for each child zone. + """ manager = getattr(nova, collection) - result = None - try: + + # NOTE(comstud): This is not ideal, but we have to do this based on + # how novaclient is implemented right now. + # 'find' is special cased as novaclient requires kwargs for it to + # filter on a 'get_all'. + # Every other method first needs to do a 'get' on the first argument + # passed, which should be a UUID. If it's 'get' itself that we want, + # we just return the result. Otherwise, we next call the real method + # that's wanted... passing other arguments that may or may not exist. + if method_name in ['find', 'findall']: try: - result = manager.get(item_id) - except ValueError, e: - result = manager.find(name=item_id) + return getattr(manager, method_name)(**kwargs) + except novaclient.NotFound: + url = zone.api_url + LOG.debug(_("%(collection)s.%(method_name)s didn't find " + "anything matching '%(kwargs)s' on '%(url)s'" % + locals())) + return None + + args = list(args) + # pop off the UUID to look up + item = args.pop(0) + try: + result = manager.get(item) except novaclient.NotFound: url = zone.api_url - LOG.debug(_("%(collection)s '%(item_id)s' not found on '%(url)s'" % + LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" % locals())) return None - if method_name.lower() not in ['get', 'find']: - result = getattr(result, method_name)() + if method_name.lower() != 'get': + # if we're doing something other than 'get', call it passing args. + result = getattr(result, method_name)(*args, **kwargs) return result -def wrap_novaclient_function(f, collection, method_name, item_id): - """Appends collection, method_name and item_id to the incoming +def wrap_novaclient_function(f, collection, method_name, *args, **kwargs): + """Appends collection, method_name and arguments to the incoming (nova, zone) call from child_zone_helper.""" def inner(nova, zone): - return f(nova, zone, collection, method_name, item_id) + return f(nova, zone, collection, method_name, *args, **kwargs) return inner @@ -220,7 +241,7 @@ class reroute_compute(object): the wrapped method. (This ensures that zone-local code can continue to use integer IDs). - 4. If the item was not found, we delgate the call to a child zone + 4. If the item was not found, we delegate the call to a child zone using the UUID. """ def __init__(self, method_name): diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index e24c2256f..eb116fac4 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -180,7 +180,7 @@ class ZoneAwareScheduler(driver.Scheduler): request_spec, kwargs) return None - num_instances = request_spec['num_instances'] + num_instances = request_spec.get('num_instances', 1) LOG.debug(_("Attemping to build %d instance%s") % (num_instances, "" if num_instances == 1 else "s")) @@ -227,7 +227,7 @@ class ZoneAwareScheduler(driver.Scheduler): raise NotImplemented(_("Zone Aware Scheduler only understands " "Compute nodes (for now)")) - num_instances = request_spec['num_instances'] + num_instances = request_spec.get('num_instances', 1) instance_type = request_spec['instance_type'] weighted = [] diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py index 9a5318aee..49791053e 100644 --- a/nova/tests/scheduler/test_least_cost_scheduler.py +++ b/nova/tests/scheduler/test_least_cost_scheduler.py @@ -122,15 +122,16 @@ class LeastCostSchedulerTestCase(test.TestCase): for hostname, caps in hosts] self.assertWeights(expected, num, request_spec, hosts) - def test_fill_first_cost_fn(self): + def test_compute_fill_first_cost_fn(self): FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.fill_first_cost_fn', + 'nova.scheduler.least_cost.compute_fill_first_cost_fn', ] - FLAGS.fill_first_cost_fn_weight = 1 + FLAGS.compute_fill_first_cost_fn_weight = 1 num = 1 - request_spec = {} - hosts = self.sched.filter_hosts(num, request_spec) + instance_type = {'memory_mb': 1024} + request_spec = {'instance_type': instance_type} + hosts = self.sched.filter_hosts('compute', request_spec, None) expected = [] for idx, (hostname, caps) in enumerate(hosts): diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index 4be59d411..fea8b424d 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -1074,7 +1074,7 @@ class DynamicNovaClientTest(test.TestCase): self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeServerCollection()), - zone, "servers", "find", "name").b, 22) + zone, "servers", "find", name="test").b, 22) self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeServerCollection()), @@ -1088,7 +1088,7 @@ class DynamicNovaClientTest(test.TestCase): self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeEmptyServerCollection()), - zone, "servers", "find", "name"), None) + zone, "servers", "find", name="test"), None) self.assertEquals(api._issue_novaclient_command( FakeNovaClient(FakeEmptyServerCollection()), diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 37c6488cc..b2599f1b8 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -55,29 +55,21 @@ def fake_zone_manager_service_states(num_hosts): class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler): - def filter_hosts(self, num, specs): - # NOTE(sirp): this is returning [(hostname, services)] - return self.zone_manager.service_states.items() - - def weigh_hosts(self, num, specs, hosts): - fake_weight = 99 - weighted = [] - for hostname, caps in hosts: - weighted.append(dict(weight=fake_weight, name=hostname)) - return weighted + # No need to stub anything at the moment + pass class FakeZoneManager(zone_manager.ZoneManager): def __init__(self): self.service_states = { 'host1': { - 'compute': {'ram': 1000}, + 'compute': {'host_memory_free': 1000*1024*1024}, }, 'host2': { - 'compute': {'ram': 2000}, + 'compute': {'host_memory_free': 2000*1024*1024}, }, 'host3': { - 'compute': {'ram': 3000}, + 'compute': {'host_memory_free': 3000*1024*1024}, }, } @@ -164,13 +156,17 @@ class ZoneAwareSchedulerTestCase(test.TestCase): sched.set_zone_manager(zm) fake_context = {} - build_plan = sched.select(fake_context, {}) + build_plan = sched.select(fake_context, + {'instance_type': {'memory_mb': 512}, + 'num_instances': 4 }) - self.assertEqual(15, len(build_plan)) + # 4 from local zones, 12 from remotes + self.assertEqual(16, len(build_plan)) - hostnames = [plan_item['name'] - for plan_item in build_plan if 'name' in plan_item] - self.assertEqual(3, len(hostnames)) + hostnames = [plan_item['hostname'] + for plan_item in build_plan if 'hostname' in plan_item] + # 4 local hosts + self.assertEqual(4, len(hostnames)) def test_empty_zone_aware_scheduler(self): """ @@ -185,8 +181,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): fake_context = {} self.assertRaises(driver.NoValidHost, sched.schedule_run_instance, fake_context, 1, - dict(host_filter=None, - request_spec={'instance_type': {}})) + dict(host_filter=None, instance_type={})) def test_schedule_do_not_schedule_with_hint(self): """ -- cgit From c293506c435222d8154618ffda89108d3f1ef692 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 27 Jun 2011 15:17:19 -0700 Subject: logging fixes --- nova/scheduler/zone_aware_scheduler.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index eb116fac4..638072ee7 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -181,8 +181,7 @@ class ZoneAwareScheduler(driver.Scheduler): return None num_instances = request_spec.get('num_instances', 1) - LOG.debug(_("Attemping to build %d instance%s") % - (num_instances, "" if num_instances == 1 else "s")) + LOG.debug(_("Attemping to build %d instance(s)") % locals()) # Create build plan and provision ... build_plan = self.select(context, request_spec) @@ -245,7 +244,7 @@ class ZoneAwareScheduler(driver.Scheduler): host_list = self.filter_hosts(topic, request_spec, host_list) if not host_list: LOG.warn(_("Ran out of available hosts after weighing " - "%d of %d instances") % (i, num_instances)) + "%(i)d of %(num_instances)d instances") % locals()) break # then weigh the selected hosts. -- cgit From 9653ee5cfae198355610ff40f0820eb9071a0deb Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 28 Jun 2011 08:08:13 -0700 Subject: log formatting typo pep8 fixes --- nova/scheduler/zone_aware_scheduler.py | 3 ++- nova/tests/scheduler/test_zone_aware_scheduler.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 638072ee7..2efad7bf2 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -181,7 +181,8 @@ class ZoneAwareScheduler(driver.Scheduler): return None num_instances = request_spec.get('num_instances', 1) - LOG.debug(_("Attemping to build %d instance(s)") % locals()) + LOG.debug(_("Attempting to build %(num_instances)d instance(s)") % + locals()) # Create build plan and provision ... build_plan = self.select(context, request_spec) diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index b2599f1b8..1e23e3ee6 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -63,13 +63,13 @@ class FakeZoneManager(zone_manager.ZoneManager): def __init__(self): self.service_states = { 'host1': { - 'compute': {'host_memory_free': 1000*1024*1024}, + 'compute': {'host_memory_free': 1073741824}, }, 'host2': { - 'compute': {'host_memory_free': 2000*1024*1024}, + 'compute': {'host_memory_free': 2147483648}, }, 'host3': { - 'compute': {'host_memory_free': 3000*1024*1024}, + 'compute': {'host_memory_free': 3221225472}, }, } @@ -158,7 +158,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): fake_context = {} build_plan = sched.select(fake_context, {'instance_type': {'memory_mb': 512}, - 'num_instances': 4 }) + 'num_instances': 4}) # 4 from local zones, 12 from remotes self.assertEqual(16, len(build_plan)) -- cgit From e611d3210911bfb6276da495d0b3943d2ce1b511 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 28 Jun 2011 08:12:08 -0700 Subject: update a test docstring to make it clear we're testing multiple instance builds --- nova/tests/scheduler/test_zone_aware_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 1e23e3ee6..32f5150a5 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -146,8 +146,8 @@ class ZoneAwareSchedulerTestCase(test.TestCase): def test_zone_aware_scheduler(self): """ - Create a nested set of FakeZones, ensure that a select call returns the - appropriate build plan. + Create a nested set of FakeZones, try to build multiple instances + and ensure that a select call returns the appropriate build plan. """ sched = FakeZoneAwareScheduler() self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method) -- cgit From 37e86a230720921488ae19fc2ca92667e8be4485 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 28 Jun 2011 08:53:13 -0700 Subject: change variable names to remove future conflict with sandy's zone-offsets branch --- nova/scheduler/zone_aware_scheduler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 2efad7bf2..2e6662a79 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -192,9 +192,10 @@ class ZoneAwareScheduler(driver.Scheduler): for num in xrange(num_instances): if not build_plan: break - item = build_plan.pop(0) - self._provision_resource(context, item, instance_id, - request_spec, kwargs) + + build_plan_item = build_plan.pop(0) + self._provision_resource(context, build_plan_item, instance_id, + request_spec, kwargs) # Returning None short-circuits the routing to Compute (since # we've already done it here) -- cgit From 6af4a9ded53efe4ca5c3aad1f3a621cc73513fb0 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 29 Jun 2011 08:30:21 -0700 Subject: updated pip-requires for novaclient --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index 6e686b7e7..dec93c351 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -9,7 +9,7 @@ boto==1.9b carrot==0.10.5 eventlet lockfile==0.8 -python-novaclient==2.5.3 +python-novaclient==2.5.7 python-daemon==1.5.5 python-gflags==1.3 redis==2.0.0 -- cgit From 45e5ae28377abc0eefd2e71ef553380b25283c48 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 29 Jun 2011 09:49:19 -0700 Subject: Fanout queues use unique queue names, so the consumer should have exclusive access. This means that they also get auto deleted when we're done with them, so they're not left around on a service restart. Fixes lp:803165 --- nova/rpc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nova/rpc.py b/nova/rpc.py index 2e78a31e7..9f0b507fd 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -275,6 +275,11 @@ class FanoutAdapterConsumer(AdapterConsumer): unique = uuid.uuid4().hex self.queue = '%s_fanout_%s' % (topic, unique) self.durable = False + # Fanout creates unique queue names, so we should auto-remove + # them when done, so they're not left around on restart. + # Also, we're the only one that should be consuming. exclusive + # implies auto_delete, so we'll just set that.. + self.exclusive = True LOG.info(_('Created "%(exchange)s" fanout exchange ' 'with "%(key)s" routing key'), dict(exchange=self.exchange, key=self.routing_key)) -- cgit From 74c222b6b4042053cc8c2d0038f37b3f8ee8b9fc Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 29 Jun 2011 14:52:56 -0400 Subject: don't pass zero in to glance image service if no limit or marker are present --- nova/api/openstack/common.py | 37 +++++++++++++++------------------ nova/api/openstack/images.py | 12 +++++------ nova/tests/api/openstack/test_common.py | 12 ++++++++--- nova/tests/api/openstack/test_images.py | 20 +++++++++--------- 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 4da7ec0ef..aa8911b62 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -45,23 +45,20 @@ def get_pagination_params(request): exc.HTTPBadRequest() exceptions to be raised. """ - try: - marker = int(request.GET.get('marker', 0)) - except ValueError: - raise webob.exc.HTTPBadRequest(_('marker param must be an integer')) - - try: - limit = int(request.GET.get('limit', 0)) - except ValueError: - raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) - - if limit < 0: - raise webob.exc.HTTPBadRequest(_('limit param must be positive')) - - if marker < 0: - raise webob.exc.HTTPBadRequest(_('marker param must be positive')) - - return(marker, limit) + params = {} + for param in ['marker', 'limit']: + if not param in request.GET: + continue + try: + params[param] = int(request.GET[param]) + except ValueError: + msg = _('%s param must be an integer') % param + raise webob.exc.HTTPBadRequest(msg) + if params[param] < 0: + msg = _('%s param must be positive') % param + raise webob.exc.HTTPBadRequest(msg) + + return params def limited(items, request, max_limit=FLAGS.osapi_max_limit): @@ -100,10 +97,10 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): """Return a slice of items according to the requested marker and limit.""" - (marker, limit) = get_pagination_params(request) + params = get_pagination_params(request) - if limit == 0: - limit = max_limit + limit = params.get('limit', max_limit) + marker = params.get('marker') limit = min(max_limit, limit) start_index = 0 diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d43340e10..64d003a0f 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -181,9 +181,9 @@ class ControllerV11(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - (marker, limit) = common.get_pagination_params(req) - images = self._image_service.index( - context, filters=filters, marker=marker, limit=limit) + page_params = common.get_pagination_params(req) + images = self._image_service.index(context, filters=filters, + **page_params) builder = self.get_builder(req).build return dict(images=[builder(image, detail=False) for image in images]) @@ -195,9 +195,9 @@ class ControllerV11(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - (marker, limit) = common.get_pagination_params(req) - images = self._image_service.detail( - context, filters=filters, marker=marker, limit=limit) + page_params = common.get_pagination_params(req) + images = self._image_service.detail(context, filters=filters, + **page_params) builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index 9a9d9125c..29cb8b944 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -161,12 +161,12 @@ class PaginationParamsTest(test.TestCase): def test_no_params(self): """ Test no params. """ req = Request.blank('/') - self.assertEqual(common.get_pagination_params(req), (0, 0)) + self.assertEqual(common.get_pagination_params(req), {}) def test_valid_marker(self): """ Test valid marker param. """ req = Request.blank('/?marker=1') - self.assertEqual(common.get_pagination_params(req), (1, 0)) + self.assertEqual(common.get_pagination_params(req), {'marker': 1}) def test_invalid_marker(self): """ Test invalid marker param. """ @@ -177,10 +177,16 @@ class PaginationParamsTest(test.TestCase): def test_valid_limit(self): """ Test valid limit param. """ req = Request.blank('/?limit=10') - self.assertEqual(common.get_pagination_params(req), (0, 10)) + self.assertEqual(common.get_pagination_params(req), {'limit': 10}) def test_invalid_limit(self): """ Test invalid limit param. """ req = Request.blank('/?limit=-2') self.assertRaises( webob.exc.HTTPBadRequest, common.get_pagination_params, req) + + def test_valid_limit_and_marker(self): + """ Test valid limit and marker parameters. """ + req = Request.blank('/?limit=20&marker=40') + self.assertEqual(common.get_pagination_params(req), + {'marker': 40, 'limit': 20}) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 446d68e9e..fc4fc84e2 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -802,7 +802,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'name': 'testname'} image_service.index( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images?name=testname') @@ -817,7 +817,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'status': 'ACTIVE'} image_service.index( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images?status=ACTIVE') @@ -832,7 +832,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'property-test': '3'} image_service.index( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images?property-test=3') @@ -847,7 +847,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'status': 'ACTIVE'} image_service.index( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images?status=ACTIVE&UNSUPPORTEDFILTER=testname') @@ -862,7 +862,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {} image_service.index( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images') @@ -877,7 +877,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'name': 'testname'} image_service.detail( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images/detail?name=testname') @@ -892,7 +892,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'status': 'ACTIVE'} image_service.detail( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images/detail?status=ACTIVE') @@ -907,7 +907,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'property-test': '3'} image_service.detail( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images/detail?property-test=3') @@ -922,7 +922,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {'status': 'ACTIVE'} image_service.detail( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images/detail?status=ACTIVE&UNSUPPORTEDFILTER=testname') @@ -937,7 +937,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): context = object() filters = {} image_service.detail( - context, filters=filters, marker=0, limit=0).AndReturn([]) + context, filters=filters).AndReturn([]) mocker.ReplayAll() request = webob.Request.blank( '/v1.1/images/detail') -- cgit From 851802e772095b646a7570bf0cc0c6d32be4643c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 29 Jun 2011 12:23:26 -0700 Subject: Fixed indentation issues Fixed min/max_count checking issues Fixed a wrongly log message when zone aware scheduler finds no suitable hosts --- nova/api/openstack/create_instance_helper.py | 11 +++++++++-- nova/api/openstack/servers.py | 12 ++++++------ nova/compute/api.py | 13 +++++++------ nova/scheduler/zone_aware_scheduler.py | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 4c6cc0f83..2cc38911a 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -116,9 +116,16 @@ class CreateInstanceHelper(object): reservation_id = body['server'].get('reservation_id') min_count = body['server'].get('min_count') max_count = body['server'].get('max_count') - if min_count: + # min_count and max_count are optional. If they exist, they come + # in as strings. We want to default 'min_count' to 1, and default + # 'max_count' to be 'min_count'. + if not min_count: + min_count = 1 + else: min_count = int(min_count) - if max_count: + if not max_count: + max_count = min_count + else: max_count = int(max_count) if min_count > max_count: min_count = max_count diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index decbfd6e6..66ef97af0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -83,11 +83,11 @@ class Controller(object): recurse_zones = query_str.get('recurse_zones') recurse_zones = recurse_zones and True or False 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'], + reservation_id=reservation_id, + project_id=project_id, + fixed_ip=fixed_ip, + recurse_zones=recurse_zones) limited_list = self._limit_items(instance_list, req) builder = self._get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] @@ -120,7 +120,7 @@ class Controller(object): result = None try: extra_values, instances = self.helper.create_instance( - req, body, self.compute_api.create) + req, body, self.compute_api.create) except faults.Fault, f: return f diff --git a/nova/compute/api.py b/nova/compute/api.py index 1092ec727..92b87e75c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -657,12 +657,13 @@ class API(base.Base): return instances 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) + 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) for zone, servers in children: for server in servers: diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 2e6662a79..9e4fc47d1 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -245,7 +245,7 @@ class ZoneAwareScheduler(driver.Scheduler): # may have been consumed from a previous build.. host_list = self.filter_hosts(topic, request_spec, host_list) if not host_list: - LOG.warn(_("Ran out of available hosts after weighing " + LOG.warn(_("Filter returned no hosts after processing " "%(i)d of %(num_instances)d instances") % locals()) break -- cgit From 7555aca28a5ab1ba4dd1be04a91bf6347eaac84f Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 29 Jun 2011 15:22:56 -0700 Subject: fix issue of recurse_zones not being converted to bool properly add bool_from_str util call add test for bool_from_str slight rework of min/max_count check --- nova/api/openstack/create_instance_helper.py | 10 ++-------- nova/api/openstack/servers.py | 3 +-- nova/tests/test_utils.py | 13 +++++++++++++ nova/utils.py | 11 +++++++++++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2cc38911a..1066713a3 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -119,14 +119,8 @@ class CreateInstanceHelper(object): # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. - if not min_count: - min_count = 1 - else: - min_count = int(min_count) - if not max_count: - max_count = min_count - else: - max_count = int(max_count) + min_count = int(min_count) if min_count else 1 + max_count = int(max_count) if max_count else min_count if min_count > max_count: min_count = max_count diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 66ef97af0..fc1ab8d46 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -80,8 +80,7 @@ class Controller(object): reservation_id = query_str.get('reservation_id') project_id = query_str.get('project_id') fixed_ip = query_str.get('fixed_ip') - recurse_zones = query_str.get('recurse_zones') - recurse_zones = recurse_zones and True or False + recurse_zones = utils.bool_from_str(query_str.get('recurse_zones')) instance_list = self.compute_api.get_all( req.environ['nova.context'], reservation_id=reservation_id, diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 3a3f914e4..0c359e981 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -276,6 +276,19 @@ class GenericUtilsTestCase(test.TestCase): result = utils.parse_server_string('www.exa:mple.com:8443') self.assertEqual(('', ''), result) + def test_bool_from_str(self): + self.assertTrue(utils.bool_from_str('1')) + self.assertTrue(utils.bool_from_str('2')) + self.assertTrue(utils.bool_from_str('-1')) + self.assertTrue(utils.bool_from_str('true')) + self.assertTrue(utils.bool_from_str('True')) + self.assertTrue(utils.bool_from_str('tRuE')) + self.assertFalse(utils.bool_from_str('False')) + self.assertFalse(utils.bool_from_str('false')) + self.assertFalse(utils.bool_from_str('0')) + self.assertFalse(utils.bool_from_str(None)) + self.assertFalse(utils.bool_from_str('junk')) + class IsUUIDLikeTestCase(test.TestCase): def assertUUIDLike(self, val, expected): diff --git a/nova/utils.py b/nova/utils.py index 510cdd9f6..be26899ca 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -772,6 +772,17 @@ def is_uuid_like(val): return (len(val) == 36) and (val.count('-') == 4) +def bool_from_str(val): + """Convert a string representation of a bool into a bool value""" + + if not val: + return False + try: + return True if int(val) else False + except ValueError: + return val.lower() == 'true' + + class Bootstrapper(object): """Provides environment bootstrapping capabilities for entry points.""" -- cgit