diff options
author | Sergey Kraynev <skraynev@mirantis.com> | 2014-07-23 06:10:02 -0400 |
---|---|---|
committer | Zane Bitter <zbitter@redhat.com> | 2014-09-29 17:09:09 -0400 |
commit | e1e852856047313159699f6a814135b6a6fde5e5 (patch) | |
tree | ce71060b064971d20665a3a10cb61a3eef4c305e /heat | |
parent | b86af2d002db2e39b4a5f9d51522f53fe1e687ae (diff) | |
download | heat-e1e852856047313159699f6a814135b6a6fde5e5.tar.gz |
Skip validation if depends on not created resource
Using references on resources defined in template may be cause
of error during validation. This fix checks reference on another
resource and skips validation if resource is in INIT state.
Change-Id: I95531a1603de1e8c4b9f0f4b05b62eebc48beb3c
Co-Authored-By: Zane Bitter <zbitter@redhat.com>
Closes-Bug: #1347571
Closes-Bug: #1339942
Diffstat (limited to 'heat')
-rw-r--r-- | heat/engine/properties.py | 9 | ||||
-rw-r--r-- | heat/tests/generic_resource.py | 9 | ||||
-rw-r--r-- | heat/tests/test_properties.py | 44 | ||||
-rw-r--r-- | heat/tests/test_resource.py | 56 |
4 files changed, 112 insertions, 6 deletions
diff --git a/heat/engine/properties.py b/heat/engine/properties.py index 70f97ff13..1a0215dbf 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -17,6 +17,7 @@ import six from heat.common import exception from heat.engine import constraints as constr +from heat.engine import function from heat.engine import parameters from heat.engine import support @@ -382,7 +383,13 @@ class Properties(collections.Mapping): if key in self.data: try: - value = self.resolve(self.data[key]) + unresolved_value = self.data[key] + if validate: + deps = function.dependencies(unresolved_value) + if any(res.action == res.INIT for res in deps): + validate = False + + value = self.resolve(unresolved_value) return prop.get_value(value, validate) # the resolver function could raise any number of exceptions, # so handle this generically diff --git a/heat/tests/generic_resource.py b/heat/tests/generic_resource.py index d212457b0..130e0577d 100644 --- a/heat/tests/generic_resource.py +++ b/heat/tests/generic_resource.py @@ -14,6 +14,8 @@ from heat.common.i18n import _ from heat.common.i18n import _LW from heat.engine import attributes +from heat.engine import constraints +from heat.engine import properties from heat.engine import resource from heat.engine import signal_responder from heat.engine import stack_user @@ -151,3 +153,10 @@ class StackUserResource(stack_user.StackUser): def handle_create(self): super(StackUserResource, self).handle_create() self.resource_id_set(self._get_user_id()) + + +class ResourceWithCustomConstraint(GenericResource): + properties_schema = \ + {'Foo': properties.Schema( + properties.Schema.STRING, + constraints=[constraints.CustomConstraint('neutron.network')])} diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index 63a47b9e7..751186287 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -15,6 +15,7 @@ import six import testtools from heat.common import exception +from heat.engine.cfn import functions as cfn_funcs from heat.engine import constraints from heat.engine.hot import parameters as hot_param from heat.engine import parameters @@ -1055,6 +1056,49 @@ class PropertiesTest(testtools.TestCase): except exception.StackValidationFailed: self.fail("Constraints should not have been evaluated.") + def test_resolve_ref_with_constraints(self): + # create test custom constraint + class IncorrectConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (Exception,) + + def validate_with_client(self, client, value): + raise Exception("Test exception") + + class TestCustomConstraint(constraints.CustomConstraint): + @property + def custom_constraint(self): + return IncorrectConstraint() + + # create schema with test constraint + schema = { + 'foo': properties.Schema( + properties.Schema.STRING, + constraints=[TestCustomConstraint('test_constraint')] + ) + } + + # define parameters for function + def test_resolver(prop): + return 'None' + + class rsrc(object): + action = INIT = "INIT" + + stack = {'another_res': rsrc()} + + # define properties with function and constraint + props = properties.Properties( + schema, + {'foo': cfn_funcs.ResourceRef( + stack, 'get_resource', 'another_res')}, + test_resolver) + + try: + self.assertIsNone(props.validate()) + except exception.StackValidationFailed: + self.fail("Constraints should not have been evaluated.") + def test_schema_from_params(self): params_snippet = { "DBUsername": { diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 27041ab2c..99dd2ac1f 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -37,6 +37,8 @@ from heat.tests.common import HeatTestCase from heat.tests import generic_resource as generic_rsrc from heat.tests import utils +import neutronclient.common.exceptions as neutron_exp + empty_template = {"HeatTemplateFormatVersion": "2012-12-12"} @@ -47,14 +49,18 @@ class ResourceTest(HeatTestCase): resource._register_class('GenericResourceType', generic_rsrc.GenericResource) + resource._register_class('ResourceWithCustomConstraint', + generic_rsrc.ResourceWithCustomConstraint) - env = environment.Environment() - env.load({u'resource_registry': - {u'OS::Test::GenericResource': u'GenericResourceType'}}) + self.env = environment.Environment() + self.env.load({u'resource_registry': + {u'OS::Test::GenericResource': u'GenericResourceType', + u'OS::Test::ResourceWithCustomConstraint': + u'ResourceWithCustomConstraint'}}) self.stack = parser.Stack(utils.dummy_context(), 'test_stack', - parser.Template(empty_template), env=env, - stack_id=str(uuid.uuid4())) + parser.Template(empty_template), + env=self.env, stack_id=str(uuid.uuid4())) self.patch('heat.engine.resource.warnings') def test_get_class_ok(self): @@ -1015,6 +1021,46 @@ class ResourceTest(HeatTestCase): mock_create.side_effect = Exception() self.assertFalse(res.is_using_neutron()) + def _test_skip_validation_if_custom_constraint(self, tmpl): + stack = parser.Stack(utils.dummy_context(), 'test', tmpl, env=self.env) + stack.store() + path = ('heat.engine.clients.os.neutron.NetworkConstraint.' + 'validate_with_client') + with mock.patch(path) as mock_validate: + mock_validate.side_effect = neutron_exp.NeutronClientException + rsrc2 = stack['bar'] + self.assertIsNone(rsrc2.validate()) + + def test_ref_skip_validation_if_custom_constraint(self): + tmpl = template.Template({ + 'HeatTemplateFormatVersion': '2012-12-12', + 'Resources': { + 'foo': {'Type': 'OS::Test::GenericResource'}, + 'bar': { + 'Type': 'OS::Test::ResourceWithCustomConstraint', + 'Properties': { + 'Foo': {'Ref': 'foo'}, + } + } + } + }) + self._test_skip_validation_if_custom_constraint(tmpl) + + def test_hot_ref_skip_validation_if_custom_constraint(self): + tmpl = template.Template({ + 'heat_template_version': '2013-05-23', + 'resources': { + 'foo': {'type': 'GenericResourceType'}, + 'bar': { + 'type': 'ResourceWithCustomConstraint', + 'properties': { + 'Foo': {'get_resource': 'foo'}, + } + } + } + }) + self._test_skip_validation_if_custom_constraint(tmpl) + class ResourceAdoptTest(HeatTestCase): def setUp(self): |