summaryrefslogtreecommitdiff
path: root/heat
diff options
context:
space:
mode:
authorSergey Kraynev <skraynev@mirantis.com>2014-07-23 06:10:02 -0400
committerZane Bitter <zbitter@redhat.com>2014-09-29 17:09:09 -0400
commite1e852856047313159699f6a814135b6a6fde5e5 (patch)
treece71060b064971d20665a3a10cb61a3eef4c305e /heat
parentb86af2d002db2e39b4a5f9d51522f53fe1e687ae (diff)
downloadheat-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.py9
-rw-r--r--heat/tests/generic_resource.py9
-rw-r--r--heat/tests/test_properties.py44
-rw-r--r--heat/tests/test_resource.py56
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):