diff options
| -rw-r--r-- | nova/api/openstack/servers.py | 2 | ||||
| -rw-r--r-- | nova/api/openstack/zones.py | 1 | ||||
| -rw-r--r-- | nova/exception.py | 2 | ||||
| -rw-r--r-- | nova/scheduler/zone_aware_scheduler.py | 177 | ||||
| -rw-r--r-- | nova/tests/test_zone_aware_scheduler.py | 143 |
5 files changed, 237 insertions, 88 deletions
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d5e78d3a..738910bc8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -189,8 +189,6 @@ class Controller(common.OpenstackController): inst['instance_type'] = inst_type inst['image_id'] = requested_image_id - LOG.debug(_("***** API.OPENSTACK.SERVER.CREATE = %(inst)s") % locals()) #pep8 - builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) server['server']['adminPass'] = password diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index f8867f5a4..411eb2b54 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -119,7 +119,6 @@ class Controller(common.OpenstackController): ctx = req.environ['nova.context'] json_specs = json.loads(req.body) specs = json.loads(json_specs) - LOG.debug("NOVA.API.OPENSTACK.ZONES.SELECT '%s'" % specs)#pep8 build_plan = api.select(ctx, specs=specs) cooked = self._scrub_build_plan(build_plan) return {"weights": cooked} diff --git a/nova/exception.py b/nova/exception.py index cf6069454..0927a42f8 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -122,7 +122,7 @@ class NotAuthorized(NovaException): message = _("Not authorized.") def __init__(self, *args, **kwargs): - super(NotFound, self).__init__(**kwargs) + super(NotAuthorized, self).__init__(**kwargs) class AdminRequired(NotAuthorized): diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 00f5660f3..58a9eca55 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -27,6 +27,7 @@ import novaclient from nova import crypto from nova import db +from nova import exception from nova import flags from nova import log as logging from nova import rpc @@ -38,6 +39,11 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.scheduler.zone_aware_scheduler') +class InvalidBlob(exception.NovaException): + message = _("Ill-formed or incorrectly routed 'blob' data sent " + "to instance create request.") + + class ZoneAwareScheduler(driver.Scheduler): """Base class for creating Zone Aware Schedulers.""" @@ -45,48 +51,6 @@ class ZoneAwareScheduler(driver.Scheduler): """Call novaclient zone method. Broken out for testing.""" return api.call_zone_method(context, method, specs=specs) - def schedule_run_instance(self, context, instance_id, request_spec, - *args, **kwargs): - """This method is called from nova.compute.api to provision - an instance. However we need to look at the parameters being - passed in to see if this is a request to: - 1. Create a Build Plan and then provision, or - 2. Use the Build Plan information in the request parameters - to simply create the instance (either in this zone or - a child zone).""" - - # TODO(sandy): We'll have to look for richer specs at some point. - - blob = request_spec.get('blob') - if blob: - self.provision_resource(context, request_spec, instance_id, - request_spec, kwargs) - return None - - # Create build plan and provision ... - build_plan = self.select(context, request_spec) - if not build_plan: - raise driver.NoValidHost(_('No hosts were available')) - - for item in build_plan: - 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) - return None - - def provision_resource(self, context, 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) - return - - self._provision_resource_from_blob(context, item, instance_id, - request_spec, kwargs) - def _provision_resource_locally(self, context, item, instance_id, kwargs): """Create the requested resource in this Zone.""" host = item['hostname'] @@ -98,48 +62,16 @@ class ZoneAwareScheduler(driver.Scheduler): LOG.debug(_("Provisioning locally via compute node %(host)s") % locals()) - def _provision_resource_from_blob(self, context, 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. - - Attempt to decrypt the blob to see if this request is: - 1. valid, and - 2. intended for this zone or a child zone. - - Note: If we have "blob" that means the request was passed - into us from a parent zone. If we have "child_blob" that - means we gathered the info from one of our children. - It's possible that, when we decrypt the 'blob' field, it - contains "child_blob" data. In which case we forward the - request.""" - - if "blob" in item: - # Request was passed in from above. Is it for us? - blob = item['blob'] - decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key) - host_info = None - try: - json_entry = decryptor(blob) - host_info = json.dumps(entry) - except M2Crypto.EVP.EVPError: - pass - elif "child_blob" in item: - # Our immediate child zone provided this info ... - host_info = item - - if not host_info: - raise exception.Invalid(_("Ill-formed or incorrectly " - "routed 'blob' data sent " - "to instance create request.") % locals()) - - # Valid data ... is it for us? - if 'child_zone' in host_info and 'child_blob' in host_info: - self._ask_child_zone_to_create_instance(context, host_info, - request_spec, kwargs) - else: - self._provision_resource_locally(context, host_info, - instance_id, kwargs) + def _decrypt_blob(self, blob): + """Returns the decrypted blob or None if invalid. Broken out + for testing.""" + decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key) + try: + json_entry = decryptor(blob) + return json.dumps(entry) + except M2Crypto.EVP.EVPError: + pass + return None def _ask_child_zone_to_create_instance(self, context, zone_info, request_spec, kwargs): @@ -179,6 +111,83 @@ class ZoneAwareScheduler(driver.Scheduler): nova.servers.create(name, image_id, flavor_id, ipgroup, meta, files, child_blob) + def _provision_resource_from_blob(self, context, 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. + + Attempt to decrypt the blob to see if this request is: + 1. valid, and + 2. intended for this zone or a child zone. + + Note: If we have "blob" that means the request was passed + into us from a parent zone. If we have "child_blob" that + means we gathered the info from one of our children. + It's possible that, when we decrypt the 'blob' field, it + contains "child_blob" data. In which case we forward the + request.""" + + host_info = None + if "blob" in item: + # Request was passed in from above. Is it for us? + host_info = self._decrypt_blob(item['blob']) + elif "child_blob" in item: + # Our immediate child zone provided this info ... + host_info = item + + if not host_info: + raise InvalidBlob() + + # Valid data ... is it for us? + if 'child_zone' in host_info and 'child_blob' in host_info: + self._ask_child_zone_to_create_instance(context, host_info, + request_spec, kwargs) + else: + self._provision_resource_locally(context, host_info, + instance_id, kwargs) + + def _provision_resource(self, context, 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) + return + + self._provision_resource_from_blob(context, item, instance_id, + request_spec, kwargs) + + def schedule_run_instance(self, context, instance_id, request_spec, + *args, **kwargs): + """This method is called from nova.compute.api to provision + an instance. However we need to look at the parameters being + passed in to see if this is a request to: + 1. Create a Build Plan and then provision, or + 2. Use the Build Plan information in the request parameters + to simply create the instance (either in this zone or + a child zone).""" + + # TODO(sandy): We'll have to look for richer specs at some point. + + blob = request_spec.get('blob') + if blob: + self._provision_resource(context, request_spec, instance_id, + request_spec, kwargs) + return None + + # Create build plan and provision ... + build_plan = self.select(context, request_spec) + if not build_plan: + raise driver.NoValidHost(_('No hosts were available')) + + for item in build_plan: + 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) + return None + def select(self, context, request_spec, *args, **kwargs): """Select returns a list of weights and zone/host information corresponding to the best hosts to service the request. Any diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/test_zone_aware_scheduler.py index 493eb294f..2ec0f2199 100644 --- a/nova/tests/test_zone_aware_scheduler.py +++ b/nova/tests/test_zone_aware_scheduler.py @@ -16,6 +16,7 @@ Tests For Zone Aware Scheduler. """ +from nova import exception from nova import test from nova.scheduler import driver from nova.scheduler import zone_aware_scheduler @@ -59,6 +60,41 @@ def fake_empty_call_zone_method(context, method, specs): 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, item, instance_id, 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): return [ ('zone1', [ @@ -120,3 +156,110 @@ class ZoneAwareSchedulerTestCase(test.TestCase): fake_context, 1, dict(host_filter=None, request_spec={'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) |
