Adds resource uuid, volume support to lifecycle scheduler hints

Heat resources are now assigned an orchestration id prior to their
instantiation by nova, cinder, et. al. This id is now added to
stack lifecycle scheduler hints. In addition to nova receiving
such hints, cinder is also now supported.

Change-Id: I5a13feb1bdedfbbe44de15e3d9eae72e56ec8a25
Closes-Bug: #1476345
This commit is contained in:
Joe D'Andrea 2015-07-20 17:03:16 -04:00
parent d5c02aaba0
commit b31259a9ed
9 changed files with 148 additions and 57 deletions

View File

@ -14,10 +14,10 @@
====================================
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.
This is a mechanism whereby when heat processes a stack Server or Volume
resource, the stack id, root stack id, stack resource uuid, stack resource
name and the path in the stack can be passed to nova and cinder by heat as
scheduler hints, to the configured schedulers for nova and cinder.
Enabling the scheduler hints
@ -28,14 +28,15 @@ 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
stack id, stack resource uuid, 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.
and cinder by heat as scheduler hints, to the configured schedulers for
nova and cinder.
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.
or cinder schedulers with stack related identifiers, for processing by
any custom scheduler plug-ins configured for nova or cinder.

View File

@ -198,18 +198,20 @@ engine_opts = [
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,'
' or volume resource are passed to the configured'
' schedulers in nova and cinder, for creates done'
' using heat resource types OS::Cinder::Volume,'
' 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.')),
' resource\'s parent stack, heat_path_in_stack will'
' be set to a list of tuples, (stackresourcename,'
' stackname) with list[0] being (None, rootstackname),'
' heat_resource_name will be set to the resource\'s'
' name, and heat_resource_uuid will be set to the'
' resource\'s orchestration id.')),
cfg.BoolOpt('encrypt_parameters_and_properties',
default=False,
help=_('Encrypt template parameters that were marked as'

View File

@ -13,7 +13,6 @@
import copy
from oslo_config import cfg
from oslo_log import log as logging
import six
@ -26,13 +25,12 @@ from heat.engine.clients import progress
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
cfg.CONF.import_opt('stack_scheduler_hints', 'heat.common.config')
from heat.engine.resources import scheduler_hints as sh
LOG = logging.getLogger(__name__)
class Instance(resource.Resource):
class Instance(resource.Resource, sh.SchedulerHintsMixin):
PROPERTIES = (
IMAGE_ID, INSTANCE_TYPE, KEY_NAME, AVAILABILITY_ZONE,
@ -529,14 +527,7 @@ 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
scheduler_hints = self._scheduler_hints(scheduler_hints)
nics = self._build_nics(self.properties[self.NETWORK_INTERFACES],
security_groups=security_groups,

View File

@ -23,13 +23,14 @@ from heat.engine.clients import progress
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources import scheduler_hints as sh
from heat.engine.resources import volume_base as vb
from heat.engine import support
LOG = logging.getLogger(__name__)
class CinderVolume(vb.BaseVolume):
class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin):
PROPERTIES = (
AVAILABILITY_ZONE, SIZE, SNAPSHOT_ID, BACKUP_ID, NAME,
@ -233,8 +234,14 @@ class CinderVolume(vb.BaseVolume):
def _create_arguments(self):
arguments = {
'size': self.properties[self.SIZE],
'availability_zone': self.properties[self.AVAILABILITY_ZONE]
'availability_zone': self.properties[self.AVAILABILITY_ZONE],
}
scheduler_hints = self._scheduler_hints(
self.properties[self.CINDER_SCHEDULER_HINTS])
if scheduler_hints:
arguments[self.CINDER_SCHEDULER_HINTS] = scheduler_hints
if self.properties[self.IMAGE]:
arguments['imageRef'] = self.client_plugin('glance').get_image_id(
self.properties[self.IMAGE])
@ -242,7 +249,7 @@ class CinderVolume(vb.BaseVolume):
arguments['imageRef'] = self.properties[self.IMAGE_REF]
optionals = (self.SNAPSHOT_ID, self.VOLUME_TYPE, self.SOURCE_VOLID,
self.METADATA, self.CINDER_SCHEDULER_HINTS)
self.METADATA)
arguments.update((prop, self.properties[prop]) for prop in optionals
if self.properties[prop])

View File

@ -31,17 +31,17 @@ from heat.engine import function
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.neutron import subnet
from heat.engine.resources import scheduler_hints as sh
from heat.engine.resources import stack_user
from heat.engine import support
from heat.rpc import api as rpc_api
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__)
class Server(stack_user.StackUser):
class Server(stack_user.StackUser, sh.SchedulerHintsMixin):
PROPERTIES = (
NAME, IMAGE, BLOCK_DEVICE_MAPPING, BLOCK_DEVICE_MAPPING_V2,
@ -706,15 +706,9 @@ class Server(stack_user.StackUser):
instance_meta = self.client_plugin().meta_serialize(
instance_meta)
scheduler_hints = self.properties[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
scheduler_hints = self._scheduler_hints(
self.properties[self.SCHEDULER_HINTS])
nics = self._build_nics(self.properties[self.NETWORKS])
block_device_mapping = self._build_block_device_mapping(
self.properties[self.BLOCK_DEVICE_MAPPING])

View File

@ -0,0 +1,45 @@
#
# 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.
from oslo_config import cfg
cfg.CONF.import_opt('stack_scheduler_hints', 'heat.common.config')
class SchedulerHintsMixin(object):
'''
Utility class to encapsulate Scheduler Hint related logic shared
between resources.
'''
HEAT_ROOT_STACK_ID = 'heat_root_stack_id'
HEAT_STACK_ID = 'heat_stack_id'
HEAT_STACK_NAME = 'heat_stack_name'
HEAT_PATH_IN_STACK = 'heat_path_in_stack'
HEAT_RESOURCE_NAME = 'heat_resource_name'
HEAT_RESOURCE_UUID = 'heat_resource_uuid'
def _scheduler_hints(self, scheduler_hints):
'''Augment scheduler hints with supplemental content.'''
if cfg.CONF.stack_scheduler_hints:
if scheduler_hints is None:
scheduler_hints = {}
scheduler_hints[self.HEAT_ROOT_STACK_ID] = \
self.stack.root_stack_id()
scheduler_hints[self.HEAT_STACK_ID] = self.stack.id
scheduler_hints[self.HEAT_STACK_NAME] = self.stack.name
scheduler_hints[self.HEAT_PATH_IN_STACK] = \
self.stack.path_in_stack()
scheduler_hints[self.HEAT_RESOURCE_NAME] = self.name
scheduler_hints[self.HEAT_RESOURCE_UUID] = self.uuid
return scheduler_hints

View File

@ -30,6 +30,7 @@ from heat.engine.clients import progress
from heat.engine import environment
from heat.engine import resource
from heat.engine.resources.aws.ec2 import instance as instances
from heat.engine.resources import scheduler_hints as sh
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template
@ -581,7 +582,7 @@ class InstancesTest(common.HeatTestCase):
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)
sh.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'
@ -592,11 +593,16 @@ class InstancesTest(common.HeatTestCase):
bdm = {"vdb": "9ef5496e-7426-446a-bbc8-01f84d9c9972:snap::True"}
self._mock_get_image_id_success('CentOS 5.2', 1)
# instance.uuid is only available once the resource has been added.
stack.add_resource(instance)
self.assertIsNotNone(instance.uuid)
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().AndReturn(self.fc)
self.stub_SnapshotConstraint_validate()
self.m.StubOutWithMock(self.fc.servers, 'create')
shm = sh.SchedulerHintsMixin
self.fc.servers.create(
image=1, flavor=1, key_name='test',
name=utils.PhysName(
@ -605,11 +611,12 @@ class InstancesTest(common.HeatTestCase):
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,
scheduler_hints={shm.HEAT_ROOT_STACK_ID: stack.root_stack_id(),
shm.HEAT_STACK_ID: stack.id,
shm.HEAT_STACK_NAME: stack.name,
shm.HEAT_PATH_IN_STACK: [(None, stack.name)],
shm.HEAT_RESOURCE_NAME: instance.name,
shm.HEAT_RESOURCE_UUID: instance.uuid,
'foo': ['spam', 'ham', 'baz'], 'bar': 'eggs'},
meta=None, nics=None, availability_zone=None,
block_device_mapping=bdm).AndReturn(

View File

@ -23,6 +23,7 @@ from heat.common import template_format
from heat.engine.clients.os import cinder
from heat.engine.clients.os import glance
from heat.engine.resources.openstack.cinder import volume as c_vol
from heat.engine.resources import scheduler_hints as sh
from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.objects import resource_data as resource_data_object
@ -887,6 +888,42 @@ class CinderVolumeTest(vt_base.BaseVolumeTest):
'volume API.', six.text_type(ex))
self.m.VerifyAll()
def test_cinder_create_with_stack_scheduler_hints(self):
fv = vt_base.FakeVolume('creating')
sh.cfg.CONF.set_override('stack_scheduler_hints', True)
stack_name = 'test_cvolume_stack_scheduler_hints_stack'
t = template_format.parse(single_cinder_volume_template)
stack = utils.parse_stack(t, stack_name=stack_name)
rsrc = stack['volume']
# rsrc.uuid is only available once the resource has been added.
stack.add_resource(rsrc)
self.assertIsNotNone(rsrc.uuid)
cinder.CinderClientPlugin._create().AndReturn(self.cinder_fc)
shm = sh.SchedulerHintsMixin
self.cinder_fc.volumes.create(
size=1, name='test_name', description='test_description',
availability_zone=None,
scheduler_hints={shm.HEAT_ROOT_STACK_ID: stack.root_stack_id(),
shm.HEAT_STACK_ID: stack.id,
shm.HEAT_STACK_NAME: stack.name,
shm.HEAT_PATH_IN_STACK: [(None, stack.name)],
shm.HEAT_RESOURCE_NAME: rsrc.name,
shm.HEAT_RESOURCE_UUID: rsrc.uuid}).AndReturn(fv)
self.cinder_fc.volumes.get(fv.id).AndReturn(fv)
fv_ready = vt_base.FakeVolume('available', id=fv.id)
self.cinder_fc.volumes.get(fv.id).AndReturn(fv_ready)
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
# this makes sure the auto increment worked on volume creation
self.assertTrue(rsrc.id > 0)
self.m.VerifyAll()
def _test_cinder_create_invalid_property_combinations(
self, stack_name, combinations, err_msg, exc):
stack = utils.parse_stack(self.t, stack_name=stack_name)

View File

@ -32,6 +32,7 @@ from heat.engine.clients.os import zaqar
from heat.engine import environment
from heat.engine import resource
from heat.engine.resources.openstack.nova import server as servers
from heat.engine.resources import scheduler_hints as sh
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template
@ -979,7 +980,7 @@ class ServersTest(common.HeatTestCase):
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)
sh.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'
@ -990,22 +991,28 @@ class ServersTest(common.HeatTestCase):
server = servers.Server(server_name,
resource_defns['WebServer'], stack)
# server.uuid is only available once the resource has been added.
stack.add_resource(server)
self.assertIsNotNone(server.uuid)
self._mock_get_image_id_success('CentOS 5.2', 1)
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().MultipleTimes().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc.servers, 'create')
shm = sh.SchedulerHintsMixin
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},
scheduler_hints={shm.HEAT_ROOT_STACK_ID: stack.root_stack_id(),
shm.HEAT_STACK_ID: stack.id,
shm.HEAT_STACK_NAME: stack.name,
shm.HEAT_PATH_IN_STACK: [(None, stack.name)],
shm.HEAT_RESOURCE_NAME: server.name,
shm.HEAT_RESOURCE_UUID: server.uuid},
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,