Implements stack lifecycle scheduler hints

This is a mechanism whereby when heat processes
a stack VM resource, the stack id, root stack id,
stack resource id, stack resource name and the
path in the stack (as a list of tuples,
(stackresourcename, stackname)) can be passed to
nova by heat as scheduler hints, to the
configured schedulers for nova.

Implements: blueprint stack-lifecycle-scheduler-hint
Co-Authored-By: Karolyn Chambers <chamberk@us.ibm.com>

Change-Id: I3e006339a41c469451bc3ee740018b285d3e0a65
This commit is contained in:
Bill Arnold 2014-05-28 18:59:45 -04:00 committed by root
parent 9a58a472ea
commit 2c1dc724ca
8 changed files with 187 additions and 1 deletions

View File

@ -53,6 +53,7 @@ Developers Documentation
architecture
pluginguide
schedulerhints
API Documentation
========================

View File

@ -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.

View File

@ -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',

View File

@ -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,

View File

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

View File

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

View File

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

View File

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