Evaluate resource conditions in-place

This allows us to parse the conditions only once instead of reparsing them
all every time we encountered a resource with a condition. It also allows
us to get rid of some of the API surface area and reduces our reliance on
some of the rest.

Partial-Bug: #1618713

Change-Id: I3c2273722171b9c4cb13ef6588f7b522b6689b1c
This commit is contained in:
Zane Bitter 2016-09-01 18:00:52 -04:00 committed by huangtianhua
parent edea94800b
commit 9bd13adeea
5 changed files with 79 additions and 42 deletions

View File

@ -111,14 +111,7 @@ class CfnTemplateBase(template_common.CommonTemplate):
def resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
def rsrc_defn_item(name, snippet):
try:
data = self.parse(stack, snippet)
self._validate_resource_definition(name, data)
except (TypeError, ValueError, KeyError) as ex:
msg = six.text_type(ex)
raise exception.StackValidationFailed(message=msg)
def build_rsrc_defn(name, data):
depends = data.get(self.RES_DEPENDS_ON)
if isinstance(depends, six.string_types):
depends = [depends]
@ -145,13 +138,33 @@ class CfnTemplateBase(template_common.CommonTemplate):
if hasattr(self, 'RES_CONDITION'):
kwargs['condition'] = data.get(self.RES_CONDITION)
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
return name, defn
return rsrc_defn.ResourceDefinition(name, **kwargs)
return dict(
rsrc_defn_item(name, data)
for name, data in resources.items() if self.get_res_condition(
stack, data, name))
conditions = template_common.Conditions(self.conditions(stack))
def defns():
for name, snippet in resources.items():
try:
data = self.parse(stack, snippet)
self._validate_resource_definition(name, data)
except (TypeError, ValueError, KeyError) as ex:
msg = six.text_type(ex)
raise exception.StackValidationFailed(message=msg)
defn = build_rsrc_defn(name, data)
cond_name = defn.condition_name()
if cond_name is not None:
path = '.'.join([self.RESOURCES,
name,
self.RES_CONDITION])
if not conditions.is_enabled(cond_name, path):
continue
yield name, defn
return dict(defns())
def add_resource(self, definition, name=None):
if name is None:

View File

@ -229,19 +229,30 @@ class HOTemplate20130523(template_common.CommonTemplate):
def resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
def rsrc_defn_from_snippet(name, snippet):
try:
data = self.parse(stack, snippet)
self._validate_resource_definition(name, data)
except (TypeError, ValueError, KeyError) as ex:
msg = six.text_type(ex)
raise exception.StackValidationFailed(message=msg)
return self.rsrc_defn_from_snippet(name, data)
conditions = template_common.Conditions(self.conditions(stack))
return dict(
(name, rsrc_defn_from_snippet(name, data))
for name, data in resources.items() if self.get_res_condition(
stack, data, name))
def defns():
for name, snippet in six.iteritems(resources):
try:
data = self.parse(stack, snippet)
self._validate_resource_definition(name, data)
except (TypeError, ValueError, KeyError) as ex:
msg = six.text_type(ex)
raise exception.StackValidationFailed(message=msg)
defn = self.rsrc_defn_from_snippet(name, data)
cond_name = defn.condition_name()
if cond_name is not None:
path = '.'.join([self.RESOURCES,
name,
self.RES_CONDITION])
if not conditions.is_enabled(cond_name, path):
continue
yield name, defn
return dict(defns())
@classmethod
def rsrc_defn_from_snippet(cls, name, data):

View File

@ -268,6 +268,13 @@ class ResourceDefinitionCore(object):
"""Return the external resource id."""
return function.resolve(self._external_id)
def condition_name(self):
"""Return the name of the conditional inclusion rule, if any.
Returns None if the resource is included unconditionally.
"""
return self._condition
def render_hot(self):
"""Return a HOT snippet for the resource definition."""
if self._rendering is None:

View File

@ -114,15 +114,6 @@ class CommonTemplate(template.Template):
def has_condition_section(self, snippet):
return False
def get_res_condition(self, stack, res_data, res_name):
"""Return the value of condition referenced by resource."""
path = ''
if self.has_condition_section(res_data):
path = '.'.join([self.RESOURCES, res_name, self.RES_CONDITION])
return self.get_condition(res_data, stack, path)
def get_output_condition(self, stack, o_data, o_key):
path = '.'.join([self.OUTPUTS, o_key, self.OUTPUT_CONDITION])
@ -158,3 +149,18 @@ class CommonTemplate(template.Template):
snippet[self.OUTPUT_VALUE] = None
return copy_outputs
class Conditions(object):
def __init__(self, conditions_dict):
self._conditions = conditions_dict
def is_enabled(self, condition_name, path):
if condition_name is None:
return True
if condition_name not in self._conditions:
raise exception.InvalidConditionReference(cd=condition_name,
path=path)
return self._conditions[condition_name]

View File

@ -32,6 +32,7 @@ from heat.engine import parameters
from heat.engine import rsrc_defn
from heat.engine import stack
from heat.engine import template
from heat.engine import template_common
from heat.tests import common
from heat.tests.openstack.nova import fakes as fakes_nova
from heat.tests import utils
@ -371,19 +372,18 @@ class TestTemplateConditionParser(common.HeatTestCase):
def test_get_res_condition_invalid(self):
tmpl = copy.deepcopy(self.tmpl)
# test condition name is invalid
tmpl.t['resources']['r1']['condition'] = 'invalid_cd'
stk = stack.Stack(self.ctx, 'test_res_invalid_condition', tmpl)
res_snippet = tmpl.t.get('resources')['r1']
conds = template_common.Conditions(tmpl.conditions(stk))
ex = self.assertRaises(exception.InvalidConditionReference,
tmpl.get_res_condition,
stk, res_snippet, 'r1')
conds.is_enabled, 'invalid_cd',
'resources.r1.condition')
self.assertIn('Invalid condition "invalid_cd" '
'(in resources.r1.condition)', six.text_type(ex))
# test condition name is not string
tmpl.t['resources']['r1']['condition'] = 111
ex = self.assertRaises(exception.InvalidConditionReference,
tmpl.get_res_condition,
stk, res_snippet, 'r1')
conds.is_enabled, 111,
'resources.r1.condition')
self.assertIn('Invalid condition "111" (in resources.r1.condition)',
six.text_type(ex))