diff --git a/doc/source/index.rst b/doc/source/index.rst index 7fa9bf8b0b..c5b433bcd9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -53,6 +53,7 @@ Developers Documentation architecture pluginguide + schedulerhints API Documentation ======================== diff --git a/doc/source/schedulerhints.rst b/doc/source/schedulerhints.rst new file mode 100644 index 0000000000..92506e6db6 --- /dev/null +++ b/doc/source/schedulerhints.rst @@ -0,0 +1,41 @@ +.. + 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. + +==================================== +Heat Stack Lifecycle Scheduler Hints +==================================== +This is a mechanism whereby when heat processes a stack Server resource, the +stack id, root stack id, stack resource id, stack resource name and the path +in the stack can be passed to nova by heat as scheduler hints, to the +configured schedulers for nova. + + +Enabling the scheduler hints +---------------------------- +By default, passing the lifecycle scheduler hints is disabled. To enable it, +set stack_scheduler_hints to True in heat.conf. + +The hints +--------- +When heat processes a stack, and the feature is enabled, the stack id, root +stack id, stack resource id, stack resource name and the path in the stack +(as a list of tuple, (stackresourcename, stackname)) will be passed to nova +by heat as scheduler hints, to the configured schedulers for nova. + +Purpose +------- +A heat provider may have a need for custom code to examine stack requests +prior to performing the operations to create or update a stack. After the +custom code completes, the provider may want to provide hints to the nova +scheduler with stack related identifiers, for processing by any custom +scheduler plug-ins configured for nova. diff --git a/heat/common/config.py b/heat/common/config.py index 0173b4b92c..4ff2533f1e 100644 --- a/heat/common/config.py +++ b/heat/common/config.py @@ -189,7 +189,24 @@ engine_opts = [ 'resource-signal using the provided keystone ' 'credentials')), cfg.StrOpt('onready', - help=_('Deprecated.'))] + help=_('Deprecated.')), + cfg.BoolOpt('stack_scheduler_hints', + default=False, + help=_('When this feature is enabled, scheduler hints' + ' identifying the heat stack context of a server' + ' resource are passed to the configured schedulers in' + ' nova, for server creates done using heat resource' + ' types OS::Nova::Server and AWS::EC2::Instance.' + ' heat_root_stack_id will be set to the id of the root' + ' stack of the resource, heat_stack_id will be set to' + ' the id of the resource\'s parent stack,' + ' heat_stack_name will be set to the name of the' + ' resource\'s parent stack, heat_path_in_stack will be' + ' set to a list of tuples,' + ' (stackresourcename, stackname) with list[0] being' + ' (None, rootstackname), and heat_resource_name will' + ' be set to the resource\'s name.'))] + rpc_opts = [ cfg.StrOpt('host', diff --git a/heat/engine/resources/aws/ec2/instance.py b/heat/engine/resources/aws/ec2/instance.py index 998a714862..c814933163 100644 --- a/heat/engine/resources/aws/ec2/instance.py +++ b/heat/engine/resources/aws/ec2/instance.py @@ -28,6 +28,7 @@ from heat.engine import scheduler from heat.engine import volume_tasks as vol_task cfg.CONF.import_opt('instance_user', 'heat.common.config') +cfg.CONF.import_opt('stack_scheduler_hints', 'heat.common.config') LOG = logging.getLogger(__name__) @@ -524,6 +525,14 @@ class Instance(resource.Resource): scheduler_hints[hint] = hint_value else: scheduler_hints = None + if cfg.CONF.stack_scheduler_hints: + if scheduler_hints is None: + scheduler_hints = {} + scheduler_hints['heat_root_stack_id'] = self.stack.root_stack.id + scheduler_hints['heat_stack_id'] = self.stack.id + scheduler_hints['heat_stack_name'] = self.stack.name + scheduler_hints['heat_path_in_stack'] = self.stack.path_in_stack() + scheduler_hints['heat_resource_name'] = self.name nics = self._build_nics(self.properties[self.NETWORK_INTERFACES], security_groups=security_groups, diff --git a/heat/engine/resources/openstack/nova/server.py b/heat/engine/resources/openstack/nova/server.py index aa77312621..36f3fa8b59 100644 --- a/heat/engine/resources/openstack/nova/server.py +++ b/heat/engine/resources/openstack/nova/server.py @@ -35,6 +35,7 @@ from heat.rpc import api as rpc_api cfg.CONF.import_opt('instance_user', 'heat.common.config') cfg.CONF.import_opt('default_software_config_transport', 'heat.common.config') +cfg.CONF.import_opt('stack_scheduler_hints', 'heat.common.config') LOG = logging.getLogger(__name__) @@ -651,6 +652,14 @@ class Server(stack_user.StackUser): instance_meta) scheduler_hints = self.properties.get(self.SCHEDULER_HINTS) + if cfg.CONF.stack_scheduler_hints: + if scheduler_hints is None: + scheduler_hints = {} + scheduler_hints['heat_root_stack_id'] = self.stack.root_stack.id + scheduler_hints['heat_stack_id'] = self.stack.id + scheduler_hints['heat_stack_name'] = self.stack.name + scheduler_hints['heat_path_in_stack'] = self.stack.path_in_stack() + scheduler_hints['heat_resource_name'] = self.name nics = self._build_nics(self.properties.get(self.NETWORKS)) block_device_mapping = self._build_block_device_mapping( self.properties.get(self.BLOCK_DEVICE_MAPPING)) diff --git a/heat/engine/stack.py b/heat/engine/stack.py index e2bcfebd28..373f2fefcb 100755 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -251,6 +251,32 @@ class Stack(collections.Mapping): return self.parent_resource.stack.root_stack return self + def object_path_in_stack(self): + ''' + If this is not nested return (None, self), else return stack resources + and stacks in path from the root stack and including this stack + + :returns: a list of (stack_resource, stack) tuples + ''' + if self.parent_resource and self.parent_resource.stack: + path = self.parent_resource.stack.object_path_in_stack() + path.extend([(self.parent_resource, self)]) + return path + return [(None, self)] + + def path_in_stack(self): + ''' + If this is not nested return (None, self.name), else return tuples of + names (stack_resource.name, stack.name) in path from the root stack and + including this stack. + + :returns: a list of (string, string) tuples. + + ''' + opis = self.object_path_in_stack() + return [(stckres.name if stckres else None, + stck.name if stck else None) for stckres, stck in opis] + def total_resources(self): ''' Return the total number of resources in a stack, including nested diff --git a/heat/tests/test_instance.py b/heat/tests/test_instance.py index 70dffafb57..890a891d23 100644 --- a/heat/tests/test_instance.py +++ b/heat/tests/test_instance.py @@ -520,6 +520,46 @@ class InstancesTest(common.HeatTestCase): self.m.VerifyAll() + def test_instance_create_with_stack_scheduler_hints(self): + return_server = self.fc.servers.list()[1] + instances.cfg.CONF.set_override('stack_scheduler_hints', True) + # Unroll _create_test_instance, to enable check + # for addition of heat ids (stack id, resource name) + stack_name = 'test_instance_create_with_stack_scheduler_hints' + (t, stack) = self._get_test_template(stack_name) + resource_defns = t.resource_definitions(stack) + instance = instances.Instance('in_create_with_sched_hints', + resource_defns['WebServer'], stack) + bdm = {"vdb": "9ef5496e-7426-446a-bbc8-01f84d9c9972:snap::True"} + self._mock_get_image_id_success('CentOS 5.2', 1) + + self.m.StubOutWithMock(nova.NovaClientPlugin, '_create') + nova.NovaClientPlugin._create().AndReturn(self.fc) + self.stub_SnapshotConstraint_validate() + + self.m.StubOutWithMock(self.fc.servers, 'create') + self.fc.servers.create( + image=1, flavor=1, key_name='test', + name=utils.PhysName( + stack_name, + instance.name, + limit=instance.physical_resource_name_limit), + security_groups=None, + userdata=mox.IgnoreArg(), + scheduler_hints={'heat_root_stack_id': stack.root_stack.id, + 'heat_stack_id': stack.id, + 'heat_stack_name': stack.name, + 'heat_path_in_stack': [(None, stack.name)], + 'heat_resource_name': instance.name, + 'foo': ['spam', 'ham', 'baz'], 'bar': 'eggs'}, + meta=None, nics=None, availability_zone=None, + block_device_mapping=bdm).AndReturn( + return_server) + self.m.ReplayAll() + scheduler.TaskRunner(instance.create)() + self.assertTrue(instance.id > 0) + self.m.VerifyAll() + def test_instance_validate(self): stack_name = 'test_instance_validate_stack' (tmpl, stack) = self._setup_test_stack(stack_name) diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py index 92e37a7165..5efc93b225 100644 --- a/heat/tests/test_server.py +++ b/heat/tests/test_server.py @@ -879,6 +879,49 @@ class ServersTest(common.HeatTestCase): config_drive=None, disk_config=None, reservation_id=None, files={}, admin_pass='foo') + def test_server_create_with_stack_scheduler_hints(self): + return_server = self.fc.servers.list()[1] + return_server.id = '5678' + servers.cfg.CONF.set_override('stack_scheduler_hints', True) + # Unroll _create_test_server, to enable check + # for addition of heat ids (stack id, resource name) + stack_name = 'test_server_w_stack_sched_hints_s' + server_name = 'server_w_stack_sched_hints' + (t, stack) = self._get_test_template(stack_name, server_name) + + resource_defns = t.resource_definitions(stack) + server = servers.Server(server_name, + resource_defns['WebServer'], stack) + + self._mock_get_image_id_success('CentOS 5.2', 1, + server_rebuild=False) + + self.m.StubOutWithMock(nova.NovaClientPlugin, '_create') + nova.NovaClientPlugin._create().MultipleTimes().AndReturn(self.fc) + + self.m.StubOutWithMock(self.fc.servers, 'create') + self.fc.servers.create( + image=1, flavor=1, key_name='test', + name=server_name, + security_groups=[], + userdata=mox.IgnoreArg(), + scheduler_hints={'heat_root_stack_id': stack.root_stack.id, + 'heat_stack_id': stack.id, + 'heat_stack_name': stack.name, + 'heat_path_in_stack': [(None, stack.name)], + 'heat_resource_name': server.name}, + meta=None, nics=None, availability_zone=None, + block_device_mapping=None, block_device_mapping_v2=None, + config_drive=None, disk_config=None, reservation_id=None, + files={}, admin_pass=None).AndReturn(return_server) + + self.m.ReplayAll() + scheduler.TaskRunner(server.create)() + # this makes sure the auto increment worked on server creation + self.assertTrue(server.id > 0) + + self.m.VerifyAll() + def test_check_maximum(self): msg = 'test_check_maximum' self.assertIsNone(servers.Server._check_maximum(1, 1, msg))