diff --git a/heat/engine/constraint/common_constraints.py b/heat/engine/constraint/common_constraints.py index 124f09eb92..653a034c3c 100644 --- a/heat/engine/constraint/common_constraints.py +++ b/heat/engine/constraint/common_constraints.py @@ -118,6 +118,25 @@ class CIDRConstraint(constraints.BaseCustomConstraint): return False +class IPCIDRConstraint(constraints.BaseCustomConstraint): + + def validate(self, value, context, template=None): + try: + if '/' in value: + msg = validators.validate_subnet(value) + else: + msg = validators.validate_ip_address(value) + if msg is not None: + self._error_message = msg + return False + else: + return True + except Exception: + self._error_message = '{} is not a valid IP or CIDR'.format( + value) + return False + + class ISO8601Constraint(constraints.BaseCustomConstraint): def validate(self, value, context, template=None): diff --git a/heat/engine/resources/openstack/neutron/port.py b/heat/engine/resources/openstack/neutron/port.py index c10ec2b637..a5491ec4af 100644 --- a/heat/engine/resources/openstack/neutron/port.py +++ b/heat/engine/resources/openstack/neutron/port.py @@ -266,7 +266,7 @@ class Port(neutron.NeutronResource): _('IP address to allow through this port.'), required=True, constraints=[ - constraints.CustomConstraint('net_cidr') + constraints.CustomConstraint('ip_or_cidr') ] ), }, diff --git a/heat/tests/constraints/test_common_constraints.py b/heat/tests/constraints/test_common_constraints.py index 665c0b9033..3270d84005 100644 --- a/heat/tests/constraints/test_common_constraints.py +++ b/heat/tests/constraints/test_common_constraints.py @@ -141,6 +141,78 @@ class TestCIDRConstraint(common.HeatTestCase): self.assertEqual(mock_validate_subnet.call_count, 2) +class TestIPCIDRConstraint(common.HeatTestCase): + + def setUp(self): + super(TestIPCIDRConstraint, self).setUp() + self.constraint = cc.IPCIDRConstraint() + + def test_valid_format(self): + validate_format = [ + '10.0.0.0/24', + '1.1.1.1', + '1.0.1.1', + '255.255.255.255', + '6000::/64', + '2002:2002::20c:29ff:fe7d:811a', + '::1', + '2002::', + '2002::1', + ] + for value in validate_format: + self.assertTrue(self.constraint.validate(value, None)) + + def test_invalid_format(self): + invalidate_format = [ + '::/129', + 'Invalid cidr', + '300.0.0.0/24', + '10.0.0.0/33', + '10.0.0/24', + '10.0/24', + '10.0.a.10/24', + '8.8.8.0/ 24', + None, + 123, + '1.1', + '1.1.', + '1.1.1', + '1.1.1.', + '1.1.1.256', + '1.a.1.1', + '2002::2001::1', + '2002::g', + '2001::0::', + '20c:29ff:fe7d:811a' + ] + for value in invalidate_format: + self.assertFalse(self.constraint.validate(value, None)) + + @mock.patch('neutron_lib.api.validators.validate_subnet') + @mock.patch('neutron_lib.api.validators.validate_ip_address') + def test_validate(self, mock_validate_ip, mock_validate_subnet): + test_formats = [ + '10.0.0/24', + '10.0/24', + '10.0.0.0/33', + + ] + for cidr in test_formats: + self.assertFalse(self.constraint.validate(cidr, None)) + mock_validate_subnet.assert_called_with(cidr) + self.assertEqual(mock_validate_subnet.call_count, 3) + + test_formats = [ + '10.0.0', + '10.0', + '10.0.0.0', + ] + for ip in test_formats: + self.assertFalse(self.constraint.validate(ip, None)) + mock_validate_ip.assert_called_with(ip) + self.assertEqual(mock_validate_ip.call_count, 3) + + class TestISO8601Constraint(common.HeatTestCase): def setUp(self): diff --git a/heat/tests/openstack/neutron/test_neutron_port.py b/heat/tests/openstack/neutron/test_neutron_port.py index b27b4eda21..149539fc8c 100644 --- a/heat/tests/openstack/neutron/test_neutron_port.py +++ b/heat/tests/openstack/neutron/test_neutron_port.py @@ -183,6 +183,8 @@ class NeutronPortTest(common.HeatTestCase): def test_allowed_address_pair(self): t = template_format.parse(neutron_port_with_address_pair_template) + t['resources']['port']['properties'][ + 'allowed_address_pairs'][0]['ip_address'] = '10.0.3.21' stack = utils.parse_stack(t) self.find_mock.return_value = 'abcd1234' @@ -200,7 +202,7 @@ class NeutronPortTest(common.HeatTestCase): self.create_mock.assert_called_once_with({'port': { 'network_id': u'abcd1234', 'allowed_address_pairs': [{ - 'ip_address': u'10.0.3.21/8', + 'ip_address': u'10.0.3.21', 'mac_address': u'00-B0-D0-86-BB-F7' }], 'name': utils.PhysName(stack.name, 'port'), diff --git a/setup.cfg b/setup.cfg index 62cdac7c0e..9706e1f1f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -101,6 +101,7 @@ heat.constraints = iso_8601 = heat.engine.constraint.common_constraints:ISO8601Constraint mac_addr = heat.engine.constraint.common_constraints:MACConstraint net_cidr = heat.engine.constraint.common_constraints:CIDRConstraint + ip_or_cidr = heat.engine.constraint.common_constraints:IPCIDRConstraint test_constr = heat.engine.constraint.common_constraints:TestConstraintDelay timezone = heat.engine.constraint.common_constraints:TimezoneConstraint # service constraints