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:
parent
b86af2d002
commit
e1e8528560
@ -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
|
||||
|
@ -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')])}
|
||||
|
@ -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": {
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user