From 09e664c0f00326d3dea8c04ac661ca0a05bbf0f5 Mon Sep 17 00:00:00 2001 From: Mike Milner Date: Thu, 23 Feb 2012 15:13:51 -0400 Subject: Include launch_index when creating instances. Fixes bug 934534. The launch_index column in the database is used as the ami-launch-index value provided by the metadata service. If launch_index is not specified it defaults to 1. This is incorrect for the single instance case (should be 0) and doesn't correctly handle multi-instance starts. This branch changes the default launch_index to 0 (to handle the single instance case) and adds the launch_index in the schedulers for multi-instance starts. Change-Id: Ifc45abf4cd9f50f732ba4a4b68c0a6242a6c9710 --- Authors | 1 + nova/compute/api.py | 6 +-- nova/scheduler/chance.py | 1 + nova/scheduler/distributed_scheduler.py | 1 + nova/scheduler/simple.py | 1 + nova/tests/scheduler/test_chance_scheduler.py | 53 +++++++++++++++++++++ nova/tests/scheduler/test_distributed_scheduler.py | 54 +++++++++++++++++++++- nova/tests/test_compute.py | 3 +- 8 files changed, 114 insertions(+), 6 deletions(-) diff --git a/Authors b/Authors index cf4b4056f7..25607abbcd 100644 --- a/Authors +++ b/Authors @@ -123,6 +123,7 @@ Michael Basnight Michael Gundlach Michael Still Mike Lundy +Mike Milner Mike Pittaro Mike Scherbakov Mikyung Kang diff --git a/nova/compute/api.py b/nova/compute/api.py index 0aee6dc2fe..d900fb77f5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -462,7 +462,7 @@ class API(base.Base): #NOTE(bcwaldon): No policy check since this is only used by scheduler and # the compute api. That should probably be cleaned up, though. def create_db_entry_for_new_instance(self, context, instance_type, image, - base_options, security_group, block_device_mapping, num=1): + base_options, security_group, block_device_mapping): """Create an entry in the DB for this new instance, including any related table updates (such as security group, etc). @@ -483,8 +483,8 @@ class API(base.Base): security_group_name) security_groups.append(group['id']) - instance = dict(launch_index=num, **base_options) - instance = self.db.instance_create(context, instance) + base_options.setdefault('launch_index', 0) + instance = self.db.instance_create(context, base_options) instance_id = instance['id'] instance_uuid = instance['uuid'] diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py index 587c11af9b..fbe36e7037 100644 --- a/nova/scheduler/chance.py +++ b/nova/scheduler/chance.py @@ -66,6 +66,7 @@ class ChanceScheduler(driver.Scheduler): instances = [] for num in xrange(num_instances): host = self._schedule(context, 'compute', request_spec, **kwargs) + request_spec['instance_properties']['launch_index'] = num instance = self.create_instance_db_entry(context, request_spec) driver.cast_to_compute_host(context, host, 'run_instance', instance_uuid=instance['uuid'], **kwargs) diff --git a/nova/scheduler/distributed_scheduler.py b/nova/scheduler/distributed_scheduler.py index debd6c0db4..0a94360459 100644 --- a/nova/scheduler/distributed_scheduler.py +++ b/nova/scheduler/distributed_scheduler.py @@ -84,6 +84,7 @@ class DistributedScheduler(driver.Scheduler): break weighted_host = weighted_hosts.pop(0) + request_spec['instance_properties']['launch_index'] = num instance = self._provision_resource(elevated, weighted_host, request_spec, kwargs) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index a02417eaf7..40c6607f69 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -97,6 +97,7 @@ class SimpleScheduler(chance.ChanceScheduler): for num in xrange(num_instances): host = self._schedule_instance(context, request_spec['instance_properties'], *_args, **_kwargs) + request_spec['instance_properties']['launch_index'] = num instance_ref = self.create_instance_db_entry(context, request_spec) driver.cast_to_compute_host(context, host, 'run_instance', diff --git a/nova/tests/scheduler/test_chance_scheduler.py b/nova/tests/scheduler/test_chance_scheduler.py index 8f82ea16f3..ed6ffeba4a 100644 --- a/nova/tests/scheduler/test_chance_scheduler.py +++ b/nova/tests/scheduler/test_chance_scheduler.py @@ -20,6 +20,8 @@ Tests For Chance Scheduler. import random +import mox + from nova import context from nova import exception from nova.scheduler import driver @@ -114,6 +116,57 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase): expected = [instance1_encoded, instance2_encoded] self.assertEqual(result, expected) + def test_scheduler_includes_launch_index(self): + ctxt = "fake-context" + instance_opts = {'fake_opt1': 'meow'} + request_spec = {'num_instances': 2, + 'instance_properties': instance_opts} + instance1 = {'uuid': 'fake-uuid1'} + instance2 = {'uuid': 'fake-uuid2'} + + # create_instance_db_entry() usually does this, but we're + # stubbing it. + def _add_uuid(num): + """Return a function that adds the provided uuid number.""" + def _add_uuid_num(_, spec): + spec['instance_properties']['uuid'] = 'fake-uuid%d' % num + return _add_uuid_num + + def _has_launch_index(expected_index): + """Return a function that verifies the expected index.""" + def _check_launch_index(value): + if 'instance_properties' in value: + if 'launch_index' in value['instance_properties']: + index = value['instance_properties']['launch_index'] + if index == expected_index: + return True + return False + return _check_launch_index + + self.mox.StubOutWithMock(self.driver, '_schedule') + self.mox.StubOutWithMock(self.driver, 'create_instance_db_entry') + self.mox.StubOutWithMock(driver, 'cast_to_compute_host') + self.mox.StubOutWithMock(driver, 'encode_instance') + # instance 1 + self.driver._schedule(ctxt, 'compute', request_spec).AndReturn('host') + self.driver.create_instance_db_entry( + ctxt, mox.Func(_has_launch_index(0)) + ).WithSideEffects(_add_uuid(1)).AndReturn(instance1) + driver.cast_to_compute_host(ctxt, 'host', 'run_instance', + instance_uuid=instance1['uuid']) + driver.encode_instance(instance1).AndReturn(instance1) + # instance 2 + self.driver._schedule(ctxt, 'compute', request_spec).AndReturn('host') + self.driver.create_instance_db_entry( + ctxt, mox.Func(_has_launch_index(1)) + ).WithSideEffects(_add_uuid(2)).AndReturn(instance2) + driver.cast_to_compute_host(ctxt, 'host', 'run_instance', + instance_uuid=instance2['uuid']) + driver.encode_instance(instance2).AndReturn(instance2) + self.mox.ReplayAll() + + self.driver.schedule_run_instance(ctxt, request_spec) + def test_basic_schedule_run_instance_no_hosts(self): ctxt = context.RequestContext('fake', 'fake', False) ctxt_elevated = 'fake-context-elevated' diff --git a/nova/tests/scheduler/test_distributed_scheduler.py b/nova/tests/scheduler/test_distributed_scheduler.py index a0deffe7bf..a8e2d9317b 100644 --- a/nova/tests/scheduler/test_distributed_scheduler.py +++ b/nova/tests/scheduler/test_distributed_scheduler.py @@ -16,21 +16,26 @@ Tests For Distributed Scheduler. """ +import mox + from nova import context from nova import exception from nova.scheduler import least_cost from nova.scheduler import host_manager +from nova.scheduler import distributed_scheduler from nova import test -from nova.tests.scheduler import fakes +from nova.tests.scheduler import fakes, test_scheduler def fake_filter_hosts(hosts, filter_properties): return list(hosts) -class DistributedSchedulerTestCase(test.TestCase): +class DistributedSchedulerTestCase(test_scheduler.SchedulerTestCase): """Test case for Distributed Scheduler.""" + driver_cls = distributed_scheduler.DistributedScheduler + def test_run_instance_no_hosts(self): """ Ensure empty hosts & child_zones result in NoValidHosts exception. @@ -76,6 +81,51 @@ class DistributedSchedulerTestCase(test.TestCase): self.assertRaises(NotImplementedError, sched._schedule, fake_context, "foo", {}) + def test_scheduler_includes_launch_index(self): + ctxt = "fake-context" + fake_kwargs = {'fake_kwarg1': 'fake_value1', + 'fake_kwarg2': 'fake_value2'} + instance_opts = {'fake_opt1': 'meow'} + request_spec = {'num_instances': 2, + 'instance_properties': instance_opts} + instance1 = {'uuid': 'fake-uuid1'} + instance2 = {'uuid': 'fake-uuid2'} + + def _has_launch_index(expected_index): + """Return a function that verifies the expected index.""" + def _check_launch_index(value): + if 'instance_properties' in value: + if 'launch_index' in value['instance_properties']: + index = value['instance_properties']['launch_index'] + if index == expected_index: + return True + return False + return _check_launch_index + + class ContextFake(object): + def elevated(self): + return ctxt + context_fake = ContextFake() + + self.mox.StubOutWithMock(self.driver, '_schedule') + self.mox.StubOutWithMock(self.driver, '_provision_resource') + + self.driver._schedule(context_fake, 'compute', + request_spec, **fake_kwargs + ).AndReturn(['host1', 'host2']) + # instance 1 + self.driver._provision_resource( + ctxt, 'host1', + mox.Func(_has_launch_index(0)), fake_kwargs).AndReturn(instance1) + # instance 2 + self.driver._provision_resource( + ctxt, 'host2', + mox.Func(_has_launch_index(1)), fake_kwargs).AndReturn(instance2) + self.mox.ReplayAll() + + self.driver.schedule_run_instance(context_fake, request_spec, + **fake_kwargs) + def test_schedule_happy_day(self): """Make sure there's nothing glaringly wrong with _schedule() by doing a happy day pass through.""" diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 522f57eb13..09e3d8aca2 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -80,7 +80,8 @@ def rpc_call_wrapper(context, topic, msg, do_cast=True): scheduler = scheduler_driver.Scheduler num_instances = request_spec.get('num_instances', 1) instances = [] - for x in xrange(num_instances): + for num in xrange(num_instances): + request_spec['instance_properties']['launch_index'] = num instance = scheduler().create_instance_db_entry( context, request_spec) encoded = scheduler_driver.encode_instance(instance) -- cgit v1.2.1