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
This commit is contained in:
Sergey Kraynev 2014-07-23 06:10:02 -04:00 committed by Zane Bitter
parent b86af2d002
commit e1e8528560
4 changed files with 112 additions and 6 deletions

View File

@ -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

View File

@ -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')])}

View File

@ -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": {

View File

@ -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):