diff --git a/heat/engine/constraint/common_constraints.py b/heat/engine/constraint/common_constraints.py index 8fc056ab55..7883cf0d8a 100644 --- a/heat/engine/constraint/common_constraints.py +++ b/heat/engine/constraint/common_constraints.py @@ -13,6 +13,7 @@ import croniter import eventlet +import json import netaddr import zoneinfo @@ -188,6 +189,19 @@ class ExpirationConstraint(constraints.BaseCustomConstraint): raise ValueError(_('Expiration time is out of date.')) except Exception as ex: self._error_message = (_( - 'Expiration {0} is invalid: {1}').format(value, - str(ex))) + 'Expiration {0} is invalid: {1}').format(value, str(ex))) + return False + + +class JsonStringConstraint(constraints.BaseCustomConstraint): + + def validate(self, value, context): + if not value: + return True + try: + json.loads(value) + return True + except json.decoder.JSONDecodeError as ex: + self._error_message = (_( + 'JSON string {0} is invalid: {1}').format(value, str(ex))) return False diff --git a/heat/engine/resources/openstack/octavia/availability_zone_profile.py b/heat/engine/resources/openstack/octavia/availability_zone_profile.py index a94880612c..28233a71db 100644 --- a/heat/engine/resources/openstack/octavia/availability_zone_profile.py +++ b/heat/engine/resources/openstack/octavia/availability_zone_profile.py @@ -12,6 +12,7 @@ # under the License. from heat.common.i18n import _ +from heat.engine import constraints from heat.engine import properties from heat.engine import resource from heat.engine import support @@ -50,7 +51,8 @@ class AvailabilityZoneProfile(resource.Resource): properties.Schema.STRING, _('JSON string containing the availability zone metadata.'), update_allowed=True, - required=True + required=True, + constraints=[constraints.CustomConstraint('json_string')] ), PROVIDER_NAME: properties.Schema( properties.Schema.STRING, diff --git a/heat/engine/resources/openstack/octavia/flavor_profile.py b/heat/engine/resources/openstack/octavia/flavor_profile.py index ef20f6aca1..9a90cf32d2 100644 --- a/heat/engine/resources/openstack/octavia/flavor_profile.py +++ b/heat/engine/resources/openstack/octavia/flavor_profile.py @@ -12,6 +12,7 @@ # under the License. from heat.common.i18n import _ +from heat.engine import constraints from heat.engine import properties from heat.engine import resource from heat.engine import support @@ -44,7 +45,8 @@ class FlavorProfile(resource.Resource): properties.Schema.STRING, _('JSON string containing the flavor metadata.'), update_allowed=True, - required=True + required=True, + constraints=[constraints.CustomConstraint('json_string')] ), PROVIDER_NAME: properties.Schema( properties.Schema.STRING, diff --git a/heat/tests/constraints/test_common_constraints.py b/heat/tests/constraints/test_common_constraints.py index 03399bf691..9af6e1dd06 100644 --- a/heat/tests/constraints/test_common_constraints.py +++ b/heat/tests/constraints/test_common_constraints.py @@ -445,3 +445,30 @@ class ExpirationConstraintTest(common.HeatTestCase): def test_validation_none(self): self.assertTrue(self.constraint.validate(None, self.ctx)) + + +class JsonStringConstraintTest(common.HeatTestCase): + + def setUp(self): + super(JsonStringConstraintTest, self).setUp() + self.ctx = utils.dummy_context() + self.constraint = cc.JsonStringConstraint() + + def test_validate_json(self): + data = '{"key": "value"}' + self.assertTrue(self.constraint.validate(data, None)) + + def test_validation_error(self): + data = '{\'key\': \'value\'}' + expected = ( + "JSON string {0} is invalid: Expecting property name " + "enclosed in double quotes: line 1 column 2 (char 1)".format(data)) + + self.assertFalse(self.constraint.validate(data, self.ctx)) + self.assertEqual( + expected, + str(self.constraint._error_message) + ) + + def test_validation_none(self): + self.assertTrue(self.constraint.validate(None, self.ctx)) diff --git a/setup.cfg b/setup.cfg index 9b7626d235..2230a16163 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,6 +107,7 @@ heat.constraints = ip_or_cidr = heat.engine.constraint.common_constraints:IPCIDRConstraint test_constr = heat.engine.constraint.common_constraints:TestConstraintDelay timezone = heat.engine.constraint.common_constraints:TimezoneConstraint + json_string = heat.engine.constraint.common_constraints:JsonStringConstraint # service constraints barbican.container = heat.engine.clients.os.barbican:ContainerConstraint barbican.secret = heat.engine.clients.os.barbican:SecretConstraint