diff --git a/contrib/rackspace/rackspace/resources/cloudnetworks.py b/contrib/rackspace/rackspace/resources/cloudnetworks.py index 30dd6aa83..c78478181 100644 --- a/contrib/rackspace/rackspace/resources/cloudnetworks.py +++ b/contrib/rackspace/rackspace/resources/cloudnetworks.py @@ -11,10 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. -import netaddr from oslo_log import log as logging -from heat.common import exception from heat.common.i18n import _ from heat.common.i18n import _LW from heat.engine import attributes @@ -79,7 +77,10 @@ class CloudNetwork(resource.Resource): properties.Schema.STRING, _("The IP block from which to allocate the network. For example, " "172.16.0.0/24 or 2001:DB8::/64."), - required=True + required=True, + constraints=[ + constraints.CustomConstraint('net_cidr') + ] ) } @@ -154,10 +155,6 @@ class CloudNetwork(resource.Resource): def validate(self): super(CloudNetwork, self).validate() - try: - netaddr.IPNetwork(self.properties[self.CIDR]) - except netaddr.core.AddrFormatError: - raise exception.StackValidationFailed(message=_("Invalid cidr")) def _resolve_attribute(self, name): net = self.network() diff --git a/contrib/rackspace/rackspace/tests/test_cloudnetworks.py b/contrib/rackspace/rackspace/tests/test_cloudnetworks.py index 1e1324ebd..80623918b 100644 --- a/contrib/rackspace/rackspace/tests/test_cloudnetworks.py +++ b/contrib/rackspace/rackspace/tests/test_cloudnetworks.py @@ -116,12 +116,15 @@ class CloudNetworkTest(common.HeatTestCase): self.assertEqual(expect_label, res.FnGetAtt('label')) self.assertEqual(expect_cidr, res.FnGetAtt('cidr')) - def test_create_bad_cider(self, mock_client): - self._template['resources']['cnw']['properties']['cidr'] = "bad cidr" + def test_create_bad_cidr(self, mock_client): + prop = self._template['resources']['cnw']['properties'] + prop['cidr'] = "bad cidr" self._parse_stack() exc = self.assertRaises(exception.StackValidationFailed, self.stack.validate) - self.assertIn("Invalid cidr", six.text_type(exc)) + self.assertIn("Invalid net cidr", six.text_type(exc)) + # reset property + prop['cidr'] = "172.16.0.0/24" def test_check(self, mock_client): self._setup_stack(mock_client) diff --git a/heat/engine/clients/os/neutron.py b/heat/engine/clients/os/neutron.py index 189a2b4fd..399801e32 100644 --- a/heat/engine/clients/os/neutron.py +++ b/heat/engine/clients/os/neutron.py @@ -12,6 +12,7 @@ # under the License. import netaddr +import six from neutronclient.common import exceptions from neutronclient.neutron import v2_0 as neutronV20 @@ -189,3 +190,21 @@ class MACConstraint(constraints.BaseCustomConstraint): def validate(self, value, context): self._error_message = 'Invalid MAC address.' return netaddr.valid_mac(value) + + +class CIDRConstraint(constraints.BaseCustomConstraint): + + def _validate_whitespace(self, data): + self._error_message = ("Invalid net cidr '%s' contains " + "whitespace" % data) + if len(data.split()) > 1: + return False + return True + + def validate(self, value, context): + try: + netaddr.IPNetwork(value) + return self._validate_whitespace(value) + except Exception as ex: + self._error_message = 'Invalid net cidr %s ' % six.text_type(ex) + return False diff --git a/heat/engine/resources/openstack/neutron/firewall.py b/heat/engine/resources/openstack/neutron/firewall.py index ba9a8943d..ac8eaaf49 100644 --- a/heat/engine/resources/openstack/neutron/firewall.py +++ b/heat/engine/resources/openstack/neutron/firewall.py @@ -298,12 +298,18 @@ class FirewallRule(neutron.NeutronResource): SOURCE_IP_ADDRESS: properties.Schema( properties.Schema.STRING, _('Source IP address or CIDR.'), - update_allowed=True + update_allowed=True, + constraints=[ + constraints.CustomConstraint('net_cidr') + ] ), DESTINATION_IP_ADDRESS: properties.Schema( properties.Schema.STRING, _('Destination IP address or CIDR.'), - update_allowed=True + update_allowed=True, + constraints=[ + constraints.CustomConstraint('net_cidr') + ] ), SOURCE_PORT: properties.Schema( properties.Schema.STRING, diff --git a/heat/engine/resources/openstack/neutron/security_group.py b/heat/engine/resources/openstack/neutron/security_group.py index 541c8f9f9..dcaa013c9 100644 --- a/heat/engine/resources/openstack/neutron/security_group.py +++ b/heat/engine/resources/openstack/neutron/security_group.py @@ -105,7 +105,10 @@ class SecurityGroup(neutron.NeutronResource): RULE_REMOTE_IP_PREFIX: properties.Schema( properties.Schema.STRING, _('The remote IP prefix (CIDR) to be associated with this ' - 'security group rule.') + 'security group rule.'), + constraints=[ + constraints.CustomConstraint('net_cidr') + ] ), } diff --git a/heat/engine/resources/openstack/neutron/subnet.py b/heat/engine/resources/openstack/neutron/subnet.py index 46d0e0585..cf850e328 100644 --- a/heat/engine/resources/openstack/neutron/subnet.py +++ b/heat/engine/resources/openstack/neutron/subnet.py @@ -84,7 +84,10 @@ class Subnet(neutron.NeutronResource): CIDR: properties.Schema( properties.Schema.STRING, _('The CIDR.'), - required=True + required=True, + constraints=[ + constraints.CustomConstraint('net_cidr') + ] ), VALUE_SPECS: properties.Schema( properties.Schema.MAP, diff --git a/heat/engine/resources/openstack/neutron/vpnservice.py b/heat/engine/resources/openstack/neutron/vpnservice.py index 07a0e1291..f7ba93c42 100644 --- a/heat/engine/resources/openstack/neutron/vpnservice.py +++ b/heat/engine/resources/openstack/neutron/vpnservice.py @@ -219,7 +219,13 @@ class IPsecSiteConnection(neutron.NeutronResource): PEER_CIDRS: properties.Schema( properties.Schema.LIST, _('Remote subnet(s) in CIDR format.'), - required=True + required=True, + schema=properties.Schema( + properties.Schema.STRING, + constraints=[ + constraints.CustomConstraint('net_cidr') + ] + ) ), MTU: properties.Schema( properties.Schema.INTEGER, diff --git a/heat/tests/neutron/test_neutron_client.py b/heat/tests/neutron/test_neutron_client.py index 82339f99e..7d33c2a04 100644 --- a/heat/tests/neutron/test_neutron_client.py +++ b/heat/tests/neutron/test_neutron_client.py @@ -254,3 +254,30 @@ class TestMACConstraint(common.HeatTestCase): ] for mac in invalidate_format: self.assertFalse(self.constraint.validate(mac, None)) + + +class TestCIDRConstraint(common.HeatTestCase): + + def setUp(self): + super(TestCIDRConstraint, self).setUp() + self.constraint = neutron.CIDRConstraint() + + def test_valid_cidr_format(self): + validate_format = [ + '10.0.0.0/24', + '6000::/64', + '8.8.8.8' + ] + for cidr in validate_format: + self.assertTrue(self.constraint.validate(cidr, None)) + + def test_invalid_cidr_format(self): + invalidate_format = [ + '::/129', + 'Invalid cidr', + '300.0.0.0/24', + '10.0.0.0/33', + '8.8.8.0/ 24' + ] + for cidr in invalidate_format: + self.assertFalse(self.constraint.validate(cidr, None)) diff --git a/setup.cfg b/setup.cfg index b0ef4b680..e8c29d1a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,7 @@ heat.constraints = trove.flavor = heat.engine.clients.os.trove:FlavorConstraint ip_addr = heat.engine.clients.os.neutron:IPConstraint mac_addr = heat.engine.clients.os.neutron:MACConstraint + net_cidr = heat.engine.clients.os.neutron:CIDRConstraint heat.stack_lifecycle_plugins =