348 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright 2011 OpenStack LLC.
 | 
						|
# All Rights Reserved.
 | 
						|
#
 | 
						|
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
						|
#    not use this file except in compliance with the License. You may obtain
 | 
						|
#    a copy of the License at
 | 
						|
#
 | 
						|
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
#    Unless required by applicable law or agreed to in writing, software
 | 
						|
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
						|
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
						|
#    License for the specific language governing permissions and limitations
 | 
						|
#    under the License.
 | 
						|
"""
 | 
						|
Tests For Zone Aware Scheduler.
 | 
						|
"""
 | 
						|
 | 
						|
import json
 | 
						|
 | 
						|
import nova.db
 | 
						|
 | 
						|
from nova import exception
 | 
						|
from nova import test
 | 
						|
from nova.scheduler import driver
 | 
						|
from nova.scheduler import zone_aware_scheduler
 | 
						|
from nova.scheduler import zone_manager
 | 
						|
 | 
						|
 | 
						|
def _host_caps(multiplier):
 | 
						|
    # Returns host capabilities in the following way:
 | 
						|
    # host1 = memory:free 10 (100max)
 | 
						|
    #         disk:available 100 (1000max)
 | 
						|
    # hostN = memory:free 10 + 10N
 | 
						|
    #         disk:available 100 + 100N
 | 
						|
    # in other words: hostN has more resources than host0
 | 
						|
    # which means ... don't go above 10 hosts.
 | 
						|
    return {'host_name-description': 'XenServer %s' % multiplier,
 | 
						|
            'host_hostname': 'xs-%s' % multiplier,
 | 
						|
            'host_memory_total': 100,
 | 
						|
            'host_memory_overhead': 10,
 | 
						|
            'host_memory_free': 10 + multiplier * 10,
 | 
						|
            'host_memory_free-computed': 10 + multiplier * 10,
 | 
						|
            'host_other-config': {},
 | 
						|
            'host_ip_address': '192.168.1.%d' % (100 + multiplier),
 | 
						|
            'host_cpu_info': {},
 | 
						|
            'disk_available': 100 + multiplier * 100,
 | 
						|
            'disk_total': 1000,
 | 
						|
            'disk_used': 0,
 | 
						|
            'host_uuid': 'xxx-%d' % multiplier,
 | 
						|
            'host_name-label': 'xs-%s' % multiplier}
 | 
						|
 | 
						|
 | 
						|
def fake_zone_manager_service_states(num_hosts):
 | 
						|
    states = {}
 | 
						|
    for x in xrange(num_hosts):
 | 
						|
        states['host%02d' % (x + 1)] = {'compute': _host_caps(x)}
 | 
						|
    return states
 | 
						|
 | 
						|
 | 
						|
class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler):
 | 
						|
    # No need to stub anything at the moment
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class FakeZoneManager(zone_manager.ZoneManager):
 | 
						|
    def __init__(self):
 | 
						|
        self.service_states = {
 | 
						|
            'host1': {
 | 
						|
                'compute': {'host_memory_free': 1073741824},
 | 
						|
            },
 | 
						|
            'host2': {
 | 
						|
                'compute': {'host_memory_free': 2147483648},
 | 
						|
            },
 | 
						|
            'host3': {
 | 
						|
                'compute': {'host_memory_free': 3221225472},
 | 
						|
            },
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
class FakeEmptyZoneManager(zone_manager.ZoneManager):
 | 
						|
    def __init__(self):
 | 
						|
        self.service_states = {}
 | 
						|
 | 
						|
 | 
						|
def fake_empty_call_zone_method(context, method, specs, zones):
 | 
						|
    return []
 | 
						|
 | 
						|
 | 
						|
# Hmm, I should probably be using mox for this.
 | 
						|
was_called = False
 | 
						|
 | 
						|
 | 
						|
def fake_provision_resource(context, item, instance_id, request_spec, kwargs):
 | 
						|
    global was_called
 | 
						|
    was_called = True
 | 
						|
 | 
						|
 | 
						|
def fake_ask_child_zone_to_create_instance(context, zone_info,
 | 
						|
                                           request_spec, kwargs):
 | 
						|
    global was_called
 | 
						|
    was_called = True
 | 
						|
 | 
						|
 | 
						|
def fake_provision_resource_locally(context, build_plan, request_spec, kwargs):
 | 
						|
    global was_called
 | 
						|
    was_called = True
 | 
						|
 | 
						|
 | 
						|
def fake_provision_resource_from_blob(context, item, instance_id,
 | 
						|
                                      request_spec, kwargs):
 | 
						|
    global was_called
 | 
						|
    was_called = True
 | 
						|
 | 
						|
 | 
						|
def fake_decrypt_blob_returns_local_info(blob):
 | 
						|
    return {'foo': True}  # values aren't important.
 | 
						|
 | 
						|
 | 
						|
def fake_decrypt_blob_returns_child_info(blob):
 | 
						|
    return {'child_zone': True,
 | 
						|
            'child_blob': True}  # values aren't important. Keys are.
 | 
						|
 | 
						|
 | 
						|
def fake_call_zone_method(context, method, specs, zones):
 | 
						|
    return [
 | 
						|
        (1, [
 | 
						|
            dict(weight=1, blob='AAAAAAA'),
 | 
						|
            dict(weight=111, blob='BBBBBBB'),
 | 
						|
            dict(weight=112, blob='CCCCCCC'),
 | 
						|
            dict(weight=113, blob='DDDDDDD'),
 | 
						|
        ]),
 | 
						|
        (2, [
 | 
						|
            dict(weight=120, blob='EEEEEEE'),
 | 
						|
            dict(weight=2, blob='FFFFFFF'),
 | 
						|
            dict(weight=122, blob='GGGGGGG'),
 | 
						|
            dict(weight=123, blob='HHHHHHH'),
 | 
						|
        ]),
 | 
						|
        (3, [
 | 
						|
            dict(weight=130, blob='IIIIIII'),
 | 
						|
            dict(weight=131, blob='JJJJJJJ'),
 | 
						|
            dict(weight=132, blob='KKKKKKK'),
 | 
						|
            dict(weight=3, blob='LLLLLLL'),
 | 
						|
        ]),
 | 
						|
    ]
 | 
						|
 | 
						|
 | 
						|
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."""
 | 
						|
 | 
						|
    def test_zone_aware_scheduler(self):
 | 
						|
        """
 | 
						|
        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)
 | 
						|
        self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all)
 | 
						|
 | 
						|
        zm = FakeZoneManager()
 | 
						|
        sched.set_zone_manager(zm)
 | 
						|
 | 
						|
        fake_context = {}
 | 
						|
        build_plan = sched.select(fake_context,
 | 
						|
                {'instance_type': {'memory_mb': 512},
 | 
						|
                    'num_instances': 4})
 | 
						|
 | 
						|
        # 4 from local zones, 12 from remotes
 | 
						|
        self.assertEqual(16, len(build_plan))
 | 
						|
 | 
						|
        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_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)
 | 
						|
 | 
						|
        fake_context = {}
 | 
						|
        self.assertRaises(driver.NoValidHost, sched.schedule_run_instance,
 | 
						|
                          fake_context, 1,
 | 
						|
                          dict(host_filter=None, instance_type={}))
 | 
						|
 | 
						|
    def test_schedule_do_not_schedule_with_hint(self):
 | 
						|
        """
 | 
						|
        Check the local/child zone routing in the run_instance() call.
 | 
						|
        If the zone_blob hint was passed in, don't re-schedule.
 | 
						|
        """
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_provision_resource', fake_provision_resource)
 | 
						|
        request_spec = {
 | 
						|
                'instance_properties': {},
 | 
						|
                'instance_type': {},
 | 
						|
                'filter_driver': 'nova.scheduler.host_filter.AllHostsFilter',
 | 
						|
                'blob': "Non-None blob data",
 | 
						|
            }
 | 
						|
 | 
						|
        result = sched.schedule_run_instance(None, 1, request_spec)
 | 
						|
        self.assertEquals(None, result)
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_provision_resource_local(self):
 | 
						|
        """Provision a resource locally or remotely."""
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_provision_resource_locally',
 | 
						|
                       fake_provision_resource_locally)
 | 
						|
 | 
						|
        request_spec = {'hostname': "foo"}
 | 
						|
        sched._provision_resource(None, request_spec, 1, request_spec, {})
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_provision_resource_remote(self):
 | 
						|
        """Provision a resource locally or remotely."""
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_provision_resource_from_blob',
 | 
						|
                       fake_provision_resource_from_blob)
 | 
						|
 | 
						|
        request_spec = {}
 | 
						|
        sched._provision_resource(None, request_spec, 1, request_spec, {})
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_provision_resource_from_blob_empty(self):
 | 
						|
        """Provision a resource locally or remotely given no hints."""
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        request_spec = {}
 | 
						|
        self.assertRaises(zone_aware_scheduler.InvalidBlob,
 | 
						|
                          sched._provision_resource_from_blob,
 | 
						|
                          None, {}, 1, {}, {})
 | 
						|
 | 
						|
    def test_provision_resource_from_blob_with_local_blob(self):
 | 
						|
        """
 | 
						|
        Provision a resource locally or remotely when blob hint passed in.
 | 
						|
        """
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_decrypt_blob',
 | 
						|
                       fake_decrypt_blob_returns_local_info)
 | 
						|
        self.stubs.Set(sched, '_provision_resource_locally',
 | 
						|
                       fake_provision_resource_locally)
 | 
						|
 | 
						|
        request_spec = {'blob': "Non-None blob data"}
 | 
						|
 | 
						|
        sched._provision_resource_from_blob(None, request_spec, 1,
 | 
						|
                                            request_spec, {})
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_provision_resource_from_blob_with_child_blob(self):
 | 
						|
        """
 | 
						|
        Provision a resource locally or remotely when child blob hint
 | 
						|
        passed in.
 | 
						|
        """
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        self.stubs.Set(sched, '_decrypt_blob',
 | 
						|
                       fake_decrypt_blob_returns_child_info)
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_ask_child_zone_to_create_instance',
 | 
						|
                       fake_ask_child_zone_to_create_instance)
 | 
						|
 | 
						|
        request_spec = {'blob': "Non-None blob data"}
 | 
						|
 | 
						|
        sched._provision_resource_from_blob(None, request_spec, 1,
 | 
						|
                                            request_spec, {})
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_provision_resource_from_blob_with_immediate_child_blob(self):
 | 
						|
        """
 | 
						|
        Provision a resource locally or remotely when blob hint passed in
 | 
						|
        from an immediate child.
 | 
						|
        """
 | 
						|
        global was_called
 | 
						|
        sched = FakeZoneAwareScheduler()
 | 
						|
        was_called = False
 | 
						|
        self.stubs.Set(sched, '_ask_child_zone_to_create_instance',
 | 
						|
                       fake_ask_child_zone_to_create_instance)
 | 
						|
 | 
						|
        request_spec = {'child_blob': True, 'child_zone': True}
 | 
						|
 | 
						|
        sched._provision_resource_from_blob(None, request_spec, 1,
 | 
						|
                                            request_spec, {})
 | 
						|
        self.assertTrue(was_called)
 | 
						|
 | 
						|
    def test_decrypt_blob(self):
 | 
						|
        """Test that the decrypt method works."""
 | 
						|
 | 
						|
        fixture = FakeZoneAwareScheduler()
 | 
						|
        test_data = {"foo": "bar"}
 | 
						|
 | 
						|
        class StubDecryptor(object):
 | 
						|
            def decryptor(self, key):
 | 
						|
                return lambda blob: blob
 | 
						|
 | 
						|
        self.stubs.Set(zone_aware_scheduler, 'crypto',
 | 
						|
                       StubDecryptor())
 | 
						|
 | 
						|
        self.assertEqual(fixture._decrypt_blob(test_data),
 | 
						|
                         json.dumps(test_data))
 |