diff --git a/neutron_lib/api/definitions/bgpvpn.py b/neutron_lib/api/definitions/bgpvpn.py new file mode 100644 index 0000000..2c4b11a --- /dev/null +++ b/neutron_lib/api/definitions/bgpvpn.py @@ -0,0 +1,187 @@ +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.api import converters +from neutron_lib.api.definitions import l3 + +# Regular expression to validate an empty string +EMPTY_REGEX = (r'^$') +# Regular expression to validate 32 bits unsigned int +UINT32_REGEX = (r'(0|[1-9]\d{0,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}' + r'|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}' + r'|429496[0-6]\d{3}|4294967[0-1]\d{2}|42949672[0-8]\d' + r'|429496729[0-5])') +# Regular expression to validate 16 bits unsigned int +UINT16_REGEX = (r'(0|[1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}' + r'|655[0-2]\d|6553[0-5])') +# Regular expression to validate 8 bits unsigned int +UINT8_REGEX = (r'(0|[1-9]\d{0,1}|1\d{2}|2[0-4]\d|25[0-5])') +# Regular expression to validate IPv4 address +IP4_REGEX = (r'(%s\.%s\.%s\.%s)') % (UINT8_REGEX, UINT8_REGEX, UINT8_REGEX, + UINT8_REGEX) +# Regular expression to validate Route Target list format +# Support of the Type 0, Type 1 and Type 2, cf. chapter 4.2 in RFC 4364 +# Also validates Route Distinguisher list format +RTRD_REGEX = (r'%s|^(%s:%s|%s:%s|%s:%s)$') % (EMPTY_REGEX, UINT16_REGEX, + UINT32_REGEX, IP4_REGEX, + UINT16_REGEX, UINT32_REGEX, + UINT16_REGEX) + +# The alias of the extension. +ALIAS = 'bgpvpn' +LABEL = 'BGPVPN' + +# Whether or not this extension is simply signaling behavior to the user +# or it actively modifies the attribute map. +IS_SHIM_EXTENSION = False + +# Whether the extension is marking the adoption of standardattr model for +# legacy resources, or introducing new standardattr attributes. False or +# None if the standardattr model is adopted since the introduction of +# resource extension. +# If this is True, the alias for the extension should be prefixed with +# 'standard-attr-'. +IS_STANDARD_ATTR_EXTENSION = False + +# The name of the extension. +NAME = 'BGPVPN Extension' + +# The description of the extension. +DESCRIPTION = "Provides support for BGP VPN interconnections" + +# A timestamp of when the extension was introduced. +UPDATED_TIMESTAMP = "2014-06-10T17:00:00-00:00" + +# The specific resources and/or attributes for the extension (optional). +RESOURCE_NAME = 'bgpvpn' +COLLECTION_NAME = 'bgpvpns' +BGPVPN_L2 = 'l2' +BGPVPN_L3 = 'l3' +BGPVPN_RES = "bgpvpns" +BGPVPN_TYPES = [BGPVPN_L3, BGPVPN_L2] +NETWORK_ASSOCIATION = 'network_association' +NETWORK_ASSOCIATIONS = 'network_associations' +ROUTER_ASSOCIATION = 'router_association' +ROUTER_ASSOCIATIONS = 'router_associations' + +# The resource attribute map for the extension. +RESOURCE_ATTRIBUTE_MAP = { + COLLECTION_NAME: { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True, + 'enforce_policy': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True, + 'enforce_policy': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'default': '', + 'validate': {'type:string': None}, + 'is_visible': True, + 'enforce_policy': True}, + 'type': {'allow_post': True, 'allow_put': False, + 'default': BGPVPN_L3, + 'validate': {'type:values': BGPVPN_TYPES}, + 'is_visible': True, + 'enforce_policy': True}, + 'route_targets': {'allow_post': True, 'allow_put': True, + 'default': [], + 'convert_to': converters.convert_to_list, + 'validate': {'type:list_of_regex_or_none': + RTRD_REGEX}, + 'is_visible': True, + 'enforce_policy': True}, + 'import_targets': {'allow_post': True, 'allow_put': True, + 'default': [], + 'convert_to': converters.convert_to_list, + 'validate': {'type:list_of_regex_or_none': + RTRD_REGEX}, + 'is_visible': True, + 'enforce_policy': True}, + 'export_targets': {'allow_post': True, 'allow_put': True, + 'default': [], + 'convert_to': converters.convert_to_list, + 'validate': {'type:list_of_regex_or_none': + RTRD_REGEX}, + 'is_visible': True, + 'enforce_policy': True}, + 'route_distinguishers': {'allow_post': True, 'allow_put': True, + 'default': [], + 'convert_to': converters.convert_to_list, + 'validate': {'type:list_of_regex_or_none': + RTRD_REGEX}, + 'is_visible': True, + 'enforce_policy': True}, + 'networks': {'allow_post': False, 'allow_put': False, + 'is_visible': True, + 'enforce_policy': True}, + 'routers': {'allow_post': False, 'allow_put': False, + 'is_visible': True, + 'enforce_policy': True} + }, +} + +SUB_RESOURCE_ATTRIBUTE_MAP = { + NETWORK_ASSOCIATIONS: { + 'parent': {'collection_name': COLLECTION_NAME, + 'member_name': RESOURCE_NAME}, + 'parameters': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True, + 'enforce_policy': True}, + 'network_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'enforce_policy': True} + } + }, + ROUTER_ASSOCIATIONS: { + 'parent': {'collection_name': COLLECTION_NAME, + 'member_name': RESOURCE_NAME}, + 'parameters': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True, + 'enforce_policy': True}, + 'router_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'enforce_policy': True} + } + } +} + +ACTION_MAP = { +} + +# The list of required extensions. +REQUIRED_EXTENSIONS = [l3.ALIAS] + +# The list of optional extensions. +OPTIONAL_EXTENSIONS = [ +] diff --git a/neutron_lib/api/validators.py b/neutron_lib/api/validators.py index 2d81451..a37990e 100644 --- a/neutron_lib/api/validators.py +++ b/neutron_lib/api/validators.py @@ -642,6 +642,19 @@ def validate_regex_or_none(data, valid_values=None): return validate_regex(data, valid_values) +def validate_list_of_regex_or_none(data, valid_values=None): + """Validate data is None or a list of items matching regex. + + :param data: A list of data to validate. + :param valid_values: The regular expression to use with re.match on + each element of the data. + :returns: None if data is None or contains matches for valid_values, + otherwise a human readable message as to why data is invalid. + """ + if data is not None: + return _validate_list_of_items(validate_regex, data, valid_values) + + def validate_subnetpool_id(data, valid_values=None): """Validate data is valid subnet pool ID. @@ -943,6 +956,7 @@ validators = {'type:dict': validate_dict, '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:list_of_regex_or_none': validate_list_of_regex_or_none, 'type:mac_address': validate_mac_address, 'type:mac_address_or_none': validate_mac_address_or_none, 'type:nameservers': validate_nameservers, diff --git a/neutron_lib/tests/unit/api/definitions/test_bgpvpn.py b/neutron_lib/tests/unit/api/definitions/test_bgpvpn.py new file mode 100644 index 0000000..86ecd76 --- /dev/null +++ b/neutron_lib/tests/unit/api/definitions/test_bgpvpn.py @@ -0,0 +1,65 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.api.definitions import bgpvpn +from neutron_lib.api import validators +from neutron_lib.tests.unit.api.definitions import base + + +class BgpvpnDefinitionTestCase(base.DefinitionBaseTestCase): + extension_module = bgpvpn + extension_resources = (bgpvpn.COLLECTION_NAME,) + extension_attributes = ('type', 'route_targets', 'import_targets', + 'export_targets', 'route_distinguishers', + 'networks', 'routers', 'router_id', 'network_id') + extension_subresources = ('network_associations', 'router_associations') + + def _data_for_invalid_rtdt(self): + values = [[':1'], + ['1:'], + ['42'], + ['65536:123456'], + ['123.456.789.123:65535'], + ['4294967296:65535'], + ['1.1.1.1:655351'], + ['4294967295:65536'], + ] + for value in values: + yield value + + def _data_for_valid_rtdt(self): + values = [['1:1'], + ['1:4294967295'], + ['65535:0'], + ['65535:4294967295'], + ['1.1.1.1:1'], + ['1.1.1.1:65535'], + ['4294967295:0'], + ['65536:65535'], + ['4294967295:65535'], + ] + for value in values: + yield value + + def test_valid_rtrd(self): + for rtrd in self._data_for_valid_rtdt(): + msg = validators.validate_list_of_regex_or_none( + rtrd, + bgpvpn.RTRD_REGEX) + self.assertIsNone(msg) + + def test_invalid_rtrd(self): + for rtrd in self._data_for_invalid_rtdt(): + msg = validators.validate_list_of_regex_or_none( + rtrd, + bgpvpn.RTRD_REGEX) + self.assertIsNotNone(msg) diff --git a/neutron_lib/tests/unit/api/test_validators.py b/neutron_lib/tests/unit/api/test_validators.py index 7aa0e62..57eca71 100644 --- a/neutron_lib/tests/unit/api/test_validators.py +++ b/neutron_lib/tests/unit/api/test_validators.py @@ -756,6 +756,20 @@ class TestAttributeValidation(base.BaseTestCase): self._test_validate_regex(validators.validate_regex_or_none, allow_none=True) + def test_validate_list_of_regex_or_none(self): + pattern = '[hc]at|^$' + + list_of_regex = ['hat', 'cat', ''] + msg = validators.validate_list_of_regex_or_none(list_of_regex, pattern) + self.assertIsNone(msg) + + list_of_regex = ['bat', 'hat', 'cat', ''] + msg = validators.validate_list_of_regex_or_none(list_of_regex, pattern) + self.assertEqual("'bat' is not a valid input", msg) + + empty_list = [] + msg = validators.validate_list_of_regex_or_none(empty_list, pattern) + def test_validate_subnetpool_id(self): msg = validators.validate_subnetpool_id(constants.IPV6_PD_POOL_ID) self.assertIsNone(msg) diff --git a/releasenotes/notes/bgpvpn-api-def-22c7072575316ddd.yaml b/releasenotes/notes/bgpvpn-api-def-22c7072575316ddd.yaml new file mode 100644 index 0000000..f8e9b68 --- /dev/null +++ b/releasenotes/notes/bgpvpn-api-def-22c7072575316ddd.yaml @@ -0,0 +1,4 @@ +--- +features: + - API definition for the ``networking-bgpvpn`` extension. + - Adds new validator ``validate_list_of_regex_or_none``.