Child Zone Weight adjustment available when adding Child Zones.
This commit is contained in:
@@ -114,7 +114,8 @@ def _process(func, zone):
|
||||
|
||||
|
||||
def call_zone_method(context, method_name, errors_to_ignore=None,
|
||||
novaclient_collection_name='zones', *args, **kwargs):
|
||||
novaclient_collection_name='zones', zones=None,
|
||||
*args, **kwargs):
|
||||
"""Returns a list of (zone, call_result) objects."""
|
||||
if not isinstance(errors_to_ignore, (list, tuple)):
|
||||
# This will also handle the default None
|
||||
@@ -122,7 +123,9 @@ def call_zone_method(context, method_name, errors_to_ignore=None,
|
||||
|
||||
pool = greenpool.GreenPool()
|
||||
results = []
|
||||
for zone in db.zone_get_all(context):
|
||||
if zones is None:
|
||||
zones = db.zone_get_all(context)
|
||||
for zone in zones:
|
||||
try:
|
||||
nova = novaclient.OpenStack(zone.username, zone.password, None,
|
||||
zone.api_url)
|
||||
|
||||
@@ -33,6 +33,7 @@ from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import rpc
|
||||
|
||||
from nova.compute import api as compute_api
|
||||
from nova.scheduler import api
|
||||
from nova.scheduler import driver
|
||||
|
||||
@@ -48,14 +49,25 @@ class InvalidBlob(exception.NovaException):
|
||||
class ZoneAwareScheduler(driver.Scheduler):
|
||||
"""Base class for creating Zone Aware Schedulers."""
|
||||
|
||||
def _call_zone_method(self, context, method, specs):
|
||||
def _call_zone_method(self, context, method, specs, zones):
|
||||
"""Call novaclient zone method. Broken out for testing."""
|
||||
return api.call_zone_method(context, method, specs=specs)
|
||||
return api.call_zone_method(context, method, specs=specs, zones=zones)
|
||||
|
||||
def _provision_resource_locally(self, context, item, instance_id, kwargs):
|
||||
def _provision_resource_locally(self, context, build_plan_item,
|
||||
request_spec, kwargs):
|
||||
"""Create the requested resource in this Zone."""
|
||||
host = item['hostname']
|
||||
host = build_plan_item['hostname']
|
||||
base_options = request_spec['instance_properties']
|
||||
|
||||
# TODO(sandy): I guess someone needs to add block_device_mapping
|
||||
# support at some point? Also, OS API has no concept of security
|
||||
# groups.
|
||||
instance = compute_api.API().create_db_entry_for_new_instance(context,
|
||||
base_options, None, [])
|
||||
|
||||
instance_id = instance['id']
|
||||
kwargs['instance_id'] = instance_id
|
||||
|
||||
rpc.cast(context,
|
||||
db.queue_get_for(context, "compute", host),
|
||||
{"method": "run_instance",
|
||||
@@ -115,8 +127,8 @@ class ZoneAwareScheduler(driver.Scheduler):
|
||||
nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files,
|
||||
child_blob, reservation_id=reservation_id)
|
||||
|
||||
def _provision_resource_from_blob(self, context, item, instance_id,
|
||||
request_spec, kwargs):
|
||||
def _provision_resource_from_blob(self, context, build_plan_item,
|
||||
instance_id, request_spec, kwargs):
|
||||
"""Create the requested resource locally or in a child zone
|
||||
based on what is stored in the zone blob info.
|
||||
|
||||
@@ -132,12 +144,12 @@ class ZoneAwareScheduler(driver.Scheduler):
|
||||
request."""
|
||||
|
||||
host_info = None
|
||||
if "blob" in item:
|
||||
if "blob" in build_plan_item:
|
||||
# Request was passed in from above. Is it for us?
|
||||
host_info = self._decrypt_blob(item['blob'])
|
||||
elif "child_blob" in item:
|
||||
host_info = self._decrypt_blob(build_plan_item['blob'])
|
||||
elif "child_blob" in build_plan_item:
|
||||
# Our immediate child zone provided this info ...
|
||||
host_info = item
|
||||
host_info = build_plan_item
|
||||
|
||||
if not host_info:
|
||||
raise InvalidBlob()
|
||||
@@ -147,19 +159,44 @@ class ZoneAwareScheduler(driver.Scheduler):
|
||||
self._ask_child_zone_to_create_instance(context, host_info,
|
||||
request_spec, kwargs)
|
||||
else:
|
||||
self._provision_resource_locally(context, host_info,
|
||||
instance_id, kwargs)
|
||||
self._provision_resource_locally(context, host_info, request_spec,
|
||||
kwargs)
|
||||
|
||||
def _provision_resource(self, context, item, instance_id, request_spec,
|
||||
kwargs):
|
||||
def _provision_resource(self, context, build_plan_item, instance_id,
|
||||
request_spec, kwargs):
|
||||
"""Create the requested resource in this Zone or a child zone."""
|
||||
if "hostname" in item:
|
||||
self._provision_resource_locally(context, item, instance_id,
|
||||
kwargs)
|
||||
if "hostname" in build_plan_item:
|
||||
self._provision_resource_locally(context, build_plan_item,
|
||||
request_spec, kwargs)
|
||||
return
|
||||
|
||||
self._provision_resource_from_blob(context, item, instance_id,
|
||||
request_spec, kwargs)
|
||||
self._provision_resource_from_blob(context, build_plan_item,
|
||||
instance_id, request_spec, kwargs)
|
||||
|
||||
def _adjust_child_weights(self, child_results, zones):
|
||||
"""Apply the Scale and Offset values from the Zone definition
|
||||
to adjust the weights returned from the child zones. Alters
|
||||
child_results in place.
|
||||
"""
|
||||
for zone, result in child_results:
|
||||
if not result:
|
||||
continue
|
||||
|
||||
for zone_rec in zones:
|
||||
if zone_rec['api_url'] != zone:
|
||||
continue
|
||||
|
||||
for item in result:
|
||||
try:
|
||||
offset = zone_rec['weight_offset']
|
||||
scale = zone_rec['weight_scale']
|
||||
raw_weight = item['weight']
|
||||
cooked_weight = offset + scale * raw_weight
|
||||
item['weight'] = cooked_weight
|
||||
item['raw_weight'] = raw_weight
|
||||
except KeyError:
|
||||
LOG.exception(_("Bad child zone scaling values "
|
||||
"for Zone: %(zone)s") % locals())
|
||||
|
||||
def schedule_run_instance(self, context, instance_id, request_spec,
|
||||
*args, **kwargs):
|
||||
@@ -261,8 +298,10 @@ class ZoneAwareScheduler(driver.Scheduler):
|
||||
|
||||
# Next, tack on the best weights from the child zones ...
|
||||
json_spec = json.dumps(request_spec)
|
||||
all_zones = db.zone_get_all(context)
|
||||
child_results = self._call_zone_method(context, "select",
|
||||
specs=json_spec)
|
||||
specs=json_spec, zones=all_zones)
|
||||
self._adjust_child_weights(child_results, all_zones)
|
||||
for child_zone, result in child_results:
|
||||
for weighting in result:
|
||||
# Remember the child_zone so we can get back to
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
Tests For Zone Aware Scheduler.
|
||||
"""
|
||||
|
||||
import nova.db
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.scheduler import driver
|
||||
@@ -79,7 +81,7 @@ class FakeEmptyZoneManager(zone_manager.ZoneManager):
|
||||
self.service_states = {}
|
||||
|
||||
|
||||
def fake_empty_call_zone_method(context, method, specs):
|
||||
def fake_empty_call_zone_method(context, method, specs, zones):
|
||||
return []
|
||||
|
||||
|
||||
@@ -98,7 +100,7 @@ def fake_ask_child_zone_to_create_instance(context, zone_info,
|
||||
was_called = True
|
||||
|
||||
|
||||
def fake_provision_resource_locally(context, item, instance_id, kwargs):
|
||||
def fake_provision_resource_locally(context, build_plan, request_spec, kwargs):
|
||||
global was_called
|
||||
was_called = True
|
||||
|
||||
@@ -118,7 +120,7 @@ def fake_decrypt_blob_returns_child_info(blob):
|
||||
'child_blob': True} # values aren't important. Keys are.
|
||||
|
||||
|
||||
def fake_call_zone_method(context, method, specs):
|
||||
def fake_call_zone_method(context, method, specs, zones):
|
||||
return [
|
||||
('zone1', [
|
||||
dict(weight=1, blob='AAAAAAA'),
|
||||
@@ -141,6 +143,20 @@ def fake_call_zone_method(context, method, specs):
|
||||
]
|
||||
|
||||
|
||||
def fake_zone_get_all(context):
|
||||
return [
|
||||
dict(id=1, api_url='zone1',
|
||||
username='admin', password='password',
|
||||
weight_offset=0.0, weight_scale=1.0),
|
||||
dict(id=2, api_url='zone2',
|
||||
username='admin', password='password',
|
||||
weight_offset=1000.0, weight_scale=1.0),
|
||||
dict(id=3, api_url='zone3',
|
||||
username='admin', password='password',
|
||||
weight_offset=0.0, weight_scale=1000.0),
|
||||
]
|
||||
|
||||
|
||||
class ZoneAwareSchedulerTestCase(test.TestCase):
|
||||
"""Test case for Zone Aware Scheduler."""
|
||||
|
||||
@@ -151,6 +167,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase):
|
||||
"""
|
||||
sched = FakeZoneAwareScheduler()
|
||||
self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method)
|
||||
self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all)
|
||||
|
||||
zm = FakeZoneManager()
|
||||
sched.set_zone_manager(zm)
|
||||
@@ -168,12 +185,33 @@ class ZoneAwareSchedulerTestCase(test.TestCase):
|
||||
# 4 local hosts
|
||||
self.assertEqual(4, len(hostnames))
|
||||
|
||||
def test_adjust_child_weights(self):
|
||||
"""Make sure the weights returned by child zones are
|
||||
properly adjusted based on the scale/offset in the zone
|
||||
db entries.
|
||||
"""
|
||||
sched = FakeZoneAwareScheduler()
|
||||
child_results = fake_call_zone_method(None, None, None, None)
|
||||
zones = fake_zone_get_all(None)
|
||||
sched._adjust_child_weights(child_results, zones)
|
||||
scaled = [130000, 131000, 132000, 3000]
|
||||
for zone, results in child_results:
|
||||
for item in results:
|
||||
w = item['weight']
|
||||
if zone == 'zone1': # No change
|
||||
self.assertTrue(w < 1000.0)
|
||||
if zone == 'zone2': # Offset +1000
|
||||
self.assertTrue(w >= 1000.0 and w < 2000)
|
||||
if zone == 'zone3': # Scale x1000
|
||||
self.assertEqual(scaled.pop(0), w)
|
||||
|
||||
def test_empty_zone_aware_scheduler(self):
|
||||
"""
|
||||
Ensure empty hosts & child_zones result in NoValidHosts exception.
|
||||
"""
|
||||
sched = FakeZoneAwareScheduler()
|
||||
self.stubs.Set(sched, '_call_zone_method', fake_empty_call_zone_method)
|
||||
self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all)
|
||||
|
||||
zm = FakeEmptyZoneManager()
|
||||
sched.set_zone_manager(zm)
|
||||
|
||||
Reference in New Issue
Block a user