summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/servers.py2
-rw-r--r--nova/api/openstack/zones.py1
-rw-r--r--nova/exception.py2
-rw-r--r--nova/scheduler/zone_aware_scheduler.py177
-rw-r--r--nova/tests/test_zone_aware_scheduler.py143
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)