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 Heat Stack Lifecycle Scheduler Hints
==================================== ====================================
This is a mechanism whereby when heat processes a stack Server resource, the This is a mechanism whereby when heat processes a stack Server or Volume
stack id, root stack id, stack resource id, stack resource name and the path resource, the stack id, root stack id, stack resource uuid, stack resource
in the stack can be passed to nova by heat as scheduler hints, to the name and the path in the stack can be passed to nova and cinder by heat as
configured schedulers for nova. scheduler hints, to the configured schedulers for nova and cinder.
Enabling the scheduler hints Enabling the scheduler hints
@ -28,14 +28,15 @@ set stack_scheduler_hints to True in heat.conf.
The hints The hints
--------- ---------
When heat processes a stack, and the feature is enabled, the stack id, root 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 (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 Purpose
------- -------
A heat provider may have a need for custom code to examine stack requests 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 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 custom code completes, the provider may want to provide hints to the nova
scheduler with stack related identifiers, for processing by any custom or cinder schedulers with stack related identifiers, for processing by
scheduler plug-ins configured for nova. any custom scheduler plug-ins configured for nova or cinder.

View File

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

View File

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

View File

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

View File

@ -31,17 +31,17 @@ from heat.engine import function
from heat.engine import properties from heat.engine import properties
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.openstack.neutron import subnet 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.resources import stack_user
from heat.engine import support from heat.engine import support
from heat.rpc import api as rpc_api from heat.rpc import api as rpc_api
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__)
class Server(stack_user.StackUser): class Server(stack_user.StackUser, sh.SchedulerHintsMixin):
PROPERTIES = ( PROPERTIES = (
NAME, IMAGE, BLOCK_DEVICE_MAPPING, BLOCK_DEVICE_MAPPING_V2, 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 = self.client_plugin().meta_serialize(
instance_meta) instance_meta)
scheduler_hints = self.properties[self.SCHEDULER_HINTS] scheduler_hints = self._scheduler_hints(
if cfg.CONF.stack_scheduler_hints: self.properties[self.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.NETWORKS]) nics = self._build_nics(self.properties[self.NETWORKS])
block_device_mapping = self._build_block_device_mapping( block_device_mapping = self._build_block_device_mapping(
self.properties[self.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 environment
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.aws.ec2 import instance as instances 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 scheduler
from heat.engine import stack as parser from heat.engine import stack as parser
from heat.engine import template from heat.engine import template
@ -581,7 +582,7 @@ class InstancesTest(common.HeatTestCase):
def test_instance_create_with_stack_scheduler_hints(self): def test_instance_create_with_stack_scheduler_hints(self):
return_server = self.fc.servers.list()[1] 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 # Unroll _create_test_instance, to enable check
# for addition of heat ids (stack id, resource name) # for addition of heat ids (stack id, resource name)
stack_name = 'test_instance_create_with_stack_scheduler_hints' 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"} bdm = {"vdb": "9ef5496e-7426-446a-bbc8-01f84d9c9972:snap::True"}
self._mock_get_image_id_success('CentOS 5.2', 1) 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') self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().AndReturn(self.fc) nova.NovaClientPlugin._create().AndReturn(self.fc)
self.stub_SnapshotConstraint_validate() self.stub_SnapshotConstraint_validate()
self.m.StubOutWithMock(self.fc.servers, 'create') self.m.StubOutWithMock(self.fc.servers, 'create')
shm = sh.SchedulerHintsMixin
self.fc.servers.create( self.fc.servers.create(
image=1, flavor=1, key_name='test', image=1, flavor=1, key_name='test',
name=utils.PhysName( name=utils.PhysName(
@ -605,11 +611,12 @@ class InstancesTest(common.HeatTestCase):
limit=instance.physical_resource_name_limit), limit=instance.physical_resource_name_limit),
security_groups=None, security_groups=None,
userdata=mox.IgnoreArg(), userdata=mox.IgnoreArg(),
scheduler_hints={'heat_root_stack_id': stack.root_stack_id(), scheduler_hints={shm.HEAT_ROOT_STACK_ID: stack.root_stack_id(),
'heat_stack_id': stack.id, shm.HEAT_STACK_ID: stack.id,
'heat_stack_name': stack.name, shm.HEAT_STACK_NAME: stack.name,
'heat_path_in_stack': [(None, stack.name)], shm.HEAT_PATH_IN_STACK: [(None, stack.name)],
'heat_resource_name': instance.name, shm.HEAT_RESOURCE_NAME: instance.name,
shm.HEAT_RESOURCE_UUID: instance.uuid,
'foo': ['spam', 'ham', 'baz'], 'bar': 'eggs'}, 'foo': ['spam', 'ham', 'baz'], 'bar': 'eggs'},
meta=None, nics=None, availability_zone=None, meta=None, nics=None, availability_zone=None,
block_device_mapping=bdm).AndReturn( 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 cinder
from heat.engine.clients.os import glance from heat.engine.clients.os import glance
from heat.engine.resources.openstack.cinder import volume as c_vol 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 rsrc_defn
from heat.engine import scheduler from heat.engine import scheduler
from heat.objects import resource_data as resource_data_object 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)) 'volume API.', six.text_type(ex))
self.m.VerifyAll() 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( def _test_cinder_create_invalid_property_combinations(
self, stack_name, combinations, err_msg, exc): self, stack_name, combinations, err_msg, exc):
stack = utils.parse_stack(self.t, stack_name=stack_name) 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 environment
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.openstack.nova import server as servers 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 scheduler
from heat.engine import stack as parser from heat.engine import stack as parser
from heat.engine import template from heat.engine import template
@ -979,7 +980,7 @@ class ServersTest(common.HeatTestCase):
def test_server_create_with_stack_scheduler_hints(self): def test_server_create_with_stack_scheduler_hints(self):
return_server = self.fc.servers.list()[1] return_server = self.fc.servers.list()[1]
return_server.id = '5678' 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 # Unroll _create_test_server, to enable check
# for addition of heat ids (stack id, resource name) # for addition of heat ids (stack id, resource name)
stack_name = 'test_server_w_stack_sched_hints_s' stack_name = 'test_server_w_stack_sched_hints_s'
@ -990,22 +991,28 @@ class ServersTest(common.HeatTestCase):
server = servers.Server(server_name, server = servers.Server(server_name,
resource_defns['WebServer'], stack) 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._mock_get_image_id_success('CentOS 5.2', 1)
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create') self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().MultipleTimes().AndReturn(self.fc) nova.NovaClientPlugin._create().MultipleTimes().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc.servers, 'create') self.m.StubOutWithMock(self.fc.servers, 'create')
shm = sh.SchedulerHintsMixin
self.fc.servers.create( self.fc.servers.create(
image=1, flavor=1, key_name='test', image=1, flavor=1, key_name='test',
name=server_name, name=server_name,
security_groups=[], security_groups=[],
userdata=mox.IgnoreArg(), userdata=mox.IgnoreArg(),
scheduler_hints={'heat_root_stack_id': stack.root_stack_id(), scheduler_hints={shm.HEAT_ROOT_STACK_ID: stack.root_stack_id(),
'heat_stack_id': stack.id, shm.HEAT_STACK_ID: stack.id,
'heat_stack_name': stack.name, shm.HEAT_STACK_NAME: stack.name,
'heat_path_in_stack': [(None, stack.name)], shm.HEAT_PATH_IN_STACK: [(None, stack.name)],
'heat_resource_name': server.name}, shm.HEAT_RESOURCE_NAME: server.name,
shm.HEAT_RESOURCE_UUID: server.uuid},
meta=None, nics=None, availability_zone=None, meta=None, nics=None, availability_zone=None,
block_device_mapping=None, block_device_mapping_v2=None, block_device_mapping=None, block_device_mapping_v2=None,
config_drive=None, disk_config=None, reservation_id=None, config_drive=None, disk_config=None, reservation_id=None,