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