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:
parent
9a58a472ea
commit
2c1dc724ca
|
@ -53,6 +53,7 @@ Developers Documentation
|
||||||
|
|
||||||
architecture
|
architecture
|
||||||
pluginguide
|
pluginguide
|
||||||
|
schedulerhints
|
||||||
|
|
||||||
API Documentation
|
API Documentation
|
||||||
========================
|
========================
|
||||||
|
|
|
@ -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.
|
|
@ -189,7 +189,24 @@ engine_opts = [
|
||||||
'resource-signal using the provided keystone '
|
'resource-signal using the provided keystone '
|
||||||
'credentials')),
|
'credentials')),
|
||||||
cfg.StrOpt('onready',
|
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 = [
|
rpc_opts = [
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
|
|
|
@ -28,6 +28,7 @@ from heat.engine import scheduler
|
||||||
from heat.engine import volume_tasks as vol_task
|
from heat.engine import volume_tasks as vol_task
|
||||||
|
|
||||||
cfg.CONF.import_opt('instance_user', 'heat.common.config')
|
cfg.CONF.import_opt('instance_user', 'heat.common.config')
|
||||||
|
cfg.CONF.import_opt('stack_scheduler_hints', 'heat.common.config')
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -524,6 +525,14 @@ class Instance(resource.Resource):
|
||||||
scheduler_hints[hint] = hint_value
|
scheduler_hints[hint] = hint_value
|
||||||
else:
|
else:
|
||||||
scheduler_hints = None
|
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],
|
nics = self._build_nics(self.properties[self.NETWORK_INTERFACES],
|
||||||
security_groups=security_groups,
|
security_groups=security_groups,
|
||||||
|
|
|
@ -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('instance_user', 'heat.common.config')
|
||||||
cfg.CONF.import_opt('default_software_config_transport', '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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -651,6 +652,14 @@ class Server(stack_user.StackUser):
|
||||||
instance_meta)
|
instance_meta)
|
||||||
|
|
||||||
scheduler_hints = self.properties.get(self.SCHEDULER_HINTS)
|
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))
|
nics = self._build_nics(self.properties.get(self.NETWORKS))
|
||||||
block_device_mapping = self._build_block_device_mapping(
|
block_device_mapping = self._build_block_device_mapping(
|
||||||
self.properties.get(self.BLOCK_DEVICE_MAPPING))
|
self.properties.get(self.BLOCK_DEVICE_MAPPING))
|
||||||
|
|
|
@ -251,6 +251,32 @@ class Stack(collections.Mapping):
|
||||||
return self.parent_resource.stack.root_stack
|
return self.parent_resource.stack.root_stack
|
||||||
return self
|
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):
|
def total_resources(self):
|
||||||
'''
|
'''
|
||||||
Return the total number of resources in a stack, including nested
|
Return the total number of resources in a stack, including nested
|
||||||
|
|
|
@ -520,6 +520,46 @@ class InstancesTest(common.HeatTestCase):
|
||||||
|
|
||||||
self.m.VerifyAll()
|
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):
|
def test_instance_validate(self):
|
||||||
stack_name = 'test_instance_validate_stack'
|
stack_name = 'test_instance_validate_stack'
|
||||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||||
|
|
|
@ -879,6 +879,49 @@ class ServersTest(common.HeatTestCase):
|
||||||
config_drive=None, disk_config=None, reservation_id=None,
|
config_drive=None, disk_config=None, reservation_id=None,
|
||||||
files={}, admin_pass='foo')
|
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):
|
def test_check_maximum(self):
|
||||||
msg = 'test_check_maximum'
|
msg = 'test_check_maximum'
|
||||||
self.assertIsNone(servers.Server._check_maximum(1, 1, msg))
|
self.assertIsNone(servers.Server._check_maximum(1, 1, msg))
|
||||||
|
|
Loading…
Reference in New Issue