diff --git a/neutron_lib/api/validators.py b/neutron_lib/api/validators.py index b8f5c6b55..c6f1ad8de 100644 --- a/neutron_lib/api/validators.py +++ b/neutron_lib/api/validators.py @@ -540,6 +540,22 @@ def validate_ip_address_or_none(data, valid_values=None): return validate_ip_address(data, valid_values) +def validate_ip_or_subnet_or_none(data, valid_values=None): + """Validate data is an IP address, a valid IP subnet string, or None. + + :param data: The data to validate. + :param valid_values: Not used! + :return: None if data is None or a valid IP address or a valid IP subnet, + otherwise a human readable message indicating why the data is neither an + IP address nor IP subnet. + """ + msg_ip = validate_ip_address_or_none(data) + msg_subnet = validate_subnet_or_none(data) + if msg_ip is not None and msg_subnet is not None: + return _("'%(data)s' is neither a valid IP address, nor " + "is it a valid IP subnet") % {'data': data} + + def validate_subnet(data, valid_values=None): """Validate data is an IP network subnet string. @@ -888,6 +904,7 @@ validators = {'type:dict': validate_dict, 'type:hostroutes': validate_hostroutes, 'type:ip_address': validate_ip_address, 'type:ip_address_or_none': validate_ip_address_or_none, + 'type:ip_or_subnet_or_none': validate_ip_or_subnet_or_none, 'type:ip_pools': validate_ip_pools, 'type:mac_address': validate_mac_address, 'type:mac_address_or_none': validate_mac_address_or_none, diff --git a/neutron_lib/tests/unit/api/test_validators.py b/neutron_lib/tests/unit/api/test_validators.py index 5dbf7e963..d92f45a4e 100644 --- a/neutron_lib/tests/unit/api/test_validators.py +++ b/neutron_lib/tests/unit/api/test_validators.py @@ -1026,3 +1026,50 @@ class TestAttributeValidation(base.BaseTestCase): {'port_id': '11111111-ffff-ffff-ffff-000000000000'}, ] self.assertIsNone(validators.validate_subports(body)) + + +class TestValidateIPSubnetNone(base.BaseTestCase): + + def test_validate_none(self): + self.assertIsNone(validators.validate_ip_or_subnet_or_none(None)) + + def test_validate_ipv4(self): + testdata = "172.0.0.1" + self.assertIsNone(validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv4_subnet(self): + testdata = "172.0.0.1/24" + self.assertIsNone(validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv6(self): + testdata = "2001:0db8:0a0b:12f0:0000:0000:0000:0001" + self.assertIsNone(validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv6_subnet(self): + testdata = "::1/128" + self.assertIsNone(validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv4_invalid(self): + testdata = "300.0.0.1" + self.assertEqual(("'300.0.0.1' is neither a valid IP address, nor is " + "it a valid IP subnet"), + validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv4_subnet_invalid(self): + testdata = "172.0.0.1/45" + self.assertEqual(("'172.0.0.1/45' is neither a valid IP address, nor " + "is it a valid IP subnet"), + validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv6_invalid(self): + testdata = "xxxx:0db8:0a0b:12f0:0000:0000:0000:0001" + self.assertEqual(("'xxxx:0db8:0a0b:12f0:0000:0000:0000:0001' is " + "neither a valid IP address, nor is it a valid IP " + "subnet"), + validators.validate_ip_or_subnet_or_none(testdata)) + + def test_validate_ipv6_subnet_invalid(self): + testdata = "::1/2048" + self.assertEqual(("'::1/2048' is neither a valid IP address, nor is " + "it a valid IP subnet"), + validators.validate_ip_or_subnet_or_none(testdata)) diff --git a/releasenotes/notes/validator_ip_or_subnet_or_none-0175f906a9113954.yaml b/releasenotes/notes/validator_ip_or_subnet_or_none-0175f906a9113954.yaml new file mode 100644 index 000000000..8a7506f37 --- /dev/null +++ b/releasenotes/notes/validator_ip_or_subnet_or_none-0175f906a9113954.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added validator validate_ip_or_subnet_or_none