trunk merge

This commit is contained in:
Sandy Walsh
2011-06-29 19:36:53 -07:00
18 changed files with 340 additions and 169 deletions

View File

@@ -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'))
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)
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)
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

View File

@@ -114,6 +114,15 @@ 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')
# 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'.
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
try:
inst_type = \
@@ -137,7 +146,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:

View File

@@ -217,9 +217,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])
@@ -231,9 +231,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])

View File

@@ -76,10 +76,17 @@ 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 = utils.bool_from_str(query_str.get('recurse_zones'))
instance_list = self.compute_api.get_all(
req.environ['nova.context'],
reservation_id=reservation_id)
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']
@@ -111,14 +118,15 @@ class Controller(object):
extra_values = None
result = None
try:
extra_values, result = self.helper.create_instance(
req, body, self.compute_api.create)
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]

View File

@@ -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={},
@@ -613,17 +617,53 @@ 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)
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None, recurse_zones=False):
"""Get all instances filtered by one of the given parameters.
children = scheduler_api.call_zone_method(context, "list",
novaclient_collection_name="servers",
reservation_id=reservation_id)
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:
raise
instances = None
elif project_id or not context.is_admin:
if not context.project:
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 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,
project_id=project_id,
fixed_ip=fixed_ip,
recurse_zones=True)
for zone, servers in children:
for server in servers:
@@ -632,32 +672,6 @@ class API(base.Base):
instances.append(server._info)
return instances
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=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:
return self.get_all_across_zones(context, reservation_id)
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 not context.project:
return self.db.instance_get_all_by_user(
context, context.user_id)
if project_id is None:
project_id = context.project_id
return self.db.instance_get_all_by_project(
context, project_id)
return self.db.instance_get_all(context)
def _cast_compute_message(self, method, context, instance_id, host=None,
params=None):
"""Generic handler for RPC casts to compute.

View File

@@ -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))

View File

@@ -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(int(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):

View File

@@ -329,8 +329,9 @@ class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler):
'instance_type': <InstanceType dict>}
"""
def filter_hosts(self, num, request_spec):
def filter_hosts(self, topic, request_spec, hosts=None):
"""Filter the full host list (from the ZoneManager)"""
filter_name = request_spec.get('filter', None)
host_filter = choose_host_filter(filter_name)
@@ -341,8 +342,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]

View File

@@ -48,25 +48,43 @@ 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(LeastCostScheduler, 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 '.' 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:
# NOTE(sirp): import_class is somewhat misnamed since it can
@@ -84,23 +102,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} ]"""
[ {weight: weight, hostname: hostname, capabilities: capabs} ]
"""
# FIXME(sirp): weigh_hosts should handle more than just instances
hostnames = [hostname for hostname, caps in hosts]
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 +145,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 +169,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

View File

@@ -180,18 +180,22 @@ class ZoneAwareScheduler(driver.Scheduler):
request_spec, kwargs)
return None
num_instances = request_spec.get('num_instances', 1)
LOG.debug(_("Attempting to build %(num_instances)d instance(s)") %
locals())
# 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)
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)
@@ -224,18 +228,36 @@ 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
num_instances = request_spec.get('num_instances', 1)
instance_type = request_spec['instance_type']
# Filter local hosts based on requirements ...
host_list = self.filter_hosts(num_instances, request_spec)
weighted = []
host_list = None
# TODO(sirp): weigh_hosts should also be a function of 'topic' or
# resources, so that we can apply different objective functions to it
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:
LOG.warn(_("Filter returned no hosts after processing "
"%(i)d of %(num_instances)d instances") % locals())
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(topic, best_weight['capabilities'],
instance_type)
# Next, tack on the best weights from the child zones ...
json_spec = json.dumps(request_spec)
@@ -254,18 +276,65 @@ 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']
requested_mem = instance_type['memory_mb'] * 1024 * 1024
return capabilities['host_memory_free'] >= requested_mem
def weigh_hosts(self, num, request_spec, hosts):
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
'<topic>_filter' function more appropriate.
"""
def _default_filter(self, hostname, capabilities, request_spec):
"""Default filter function if there's no <topic>_filter"""
# NOTE(sirp): The default logic is the equivalent to
# AllHostsFilter
return True
filter_func = getattr(self, '%s_filter' % topic, _default_filter)
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 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):
"""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) * 1024 * 1024
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)

View File

@@ -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})

View File

@@ -803,7 +803,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')
@@ -818,7 +818,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')
@@ -833,7 +833,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')
@@ -848,7 +848,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')
@@ -863,7 +863,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')
@@ -878,7 +878,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')
@@ -893,7 +893,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')
@@ -908,7 +908,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')
@@ -923,7 +923,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')
@@ -938,7 +938,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')

View File

@@ -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):

View File

@@ -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()),

View File

@@ -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': 1073741824},
},
'host2': {
'compute': {'ram': 2000},
'compute': {'host_memory_free': 2147483648},
},
'host3': {
'compute': {'ram': 3000},
'compute': {'host_memory_free': 3221225472},
},
}
@@ -154,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)
@@ -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):
"""

View File

@@ -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):

View File

@@ -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."""

View File

@@ -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