From a18de8bb39b1148b40fdd1de68976b4b7f632cd5 Mon Sep 17 00:00:00 2001 From: Reedip Date: Fri, 19 Aug 2016 14:01:45 +0530 Subject: [PATCH] Add converter to convert IPv6 addresses to canonical format With respect to Section-4 of [1], the following patch adds a function to compress the extended IPv6 address passed to the neutron server to its canonical form. This allows compressed IPv6 addresses to be stored and remove any leading zeroes in the IPv6 addresses. Non-canonical IPv6 address formatted inputs wont be blocked, but the addresses would be normalized to canonical formats. [1]: http://tools.ietf.org/html/rfc5952 Partial-Bug: #1531103 Change-Id: Ic9afa5f90fba783748f7c56e8fba02e06af69748 --- doc/source/devref/api_converters.rst | 11 +++++++ neutron_lib/api/converters.py | 20 ++++++++++++ .../tests/unit/api/test_conversions.py | 31 +++++++++++++++++++ .../ipv6_address_usage-ef3d65ad5aa5798b.yaml | 6 ++++ 4 files changed, 68 insertions(+) create mode 100644 releasenotes/notes/ipv6_address_usage-ef3d65ad5aa5798b.yaml diff --git a/doc/source/devref/api_converters.rst b/doc/source/devref/api_converters.rst index 8288c42..3679b0b 100644 --- a/doc/source/devref/api_converters.rst +++ b/doc/source/devref/api_converters.rst @@ -84,3 +84,14 @@ Test The Validator Do the right thing, and make sure you've created a unit test for any converter that you add to verify that it works as expected. +IPv6 canonical address formatter +-------------------------------- + +There are several ways to display an IPv6 address, which can lead to a lot +of confusion for users, engineers and operators alike. To reduce the impact +of the multifaceted style of writing an IPv6 address, it is proposed that +the IPv6 address in Neutron should be saved in the canonical format. + +If a user passes an IPv6 address, it will be saved in the canonical format. + +The full document is found at : http://tools.ietf.org/html/rfc5952 diff --git a/neutron_lib/api/converters.py b/neutron_lib/api/converters.py index 9bb6f24..f4b43a0 100644 --- a/neutron_lib/api/converters.py +++ b/neutron_lib/api/converters.py @@ -10,10 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr from oslo_utils import strutils import six from neutron_lib._i18n import _ +from neutron_lib import constants from neutron_lib import exceptions as n_exc @@ -162,3 +164,21 @@ def convert_to_list(data): return list(data) else: return [data] + + +def convert_ip_to_canonical_format(value): + """IP Address is validated and then converted to canonical format. + + :param value: The IP Address which needs to be checked. + :returns: - None if 'value' is None, + - 'value' if 'value' is IPv4 address, + - 'value' if 'value' is not an IP Address + - canonical IPv6 address if 'value' is IPv6 address. + """ + try: + ip = netaddr.IPAddress(value) + if ip.version == constants.IP_VERSION_6: + return six.text_type(ip.format(dialect=netaddr.ipv6_compact)) + except netaddr.core.AddrFormatError: + pass + return value diff --git a/neutron_lib/tests/unit/api/test_conversions.py b/neutron_lib/tests/unit/api/test_conversions.py index 6cfea0d..4ad38db 100644 --- a/neutron_lib/tests/unit/api/test_conversions.py +++ b/neutron_lib/tests/unit/api/test_conversions.py @@ -167,3 +167,34 @@ class TestConvertToList(base.BaseTestCase): def test_convert_to_list_non_iterable(self): for item in (True, False, 1, 1.2, object()): self.assertEqual([item], converters.convert_to_list(item)) + + +class TestConvertIPv6CanonicalFormat(base.BaseTestCase): + + def test_convert_ipv6_address_extended_add_with_zeroes(self): + result = converters.convert_ip_to_canonical_format( + u'2001:0db8:0:0:0:0:0:0001') + self.assertEqual(u'2001:db8::1', result) + + @testtools.skipIf(tools.is_bsd(), 'bug/1484837') + def test_convert_ipv6_compressed_address_OSX_skip(self): + result = converters.convert_ip_to_canonical_format( + u'2001:db8:0:1:1:1:1:1') + self.assertEqual(u'2001:db8:0:1:1:1:1:1', result) + + def test_convert_ipv6_extended_addr_to_compressed(self): + result = converters.convert_ip_to_canonical_format( + u"Fe80:0:0:0:0:0:0:1") + self.assertEqual(u'fe80::1', result) + + def test_convert_ipv4_address(self): + result = converters.convert_ip_to_canonical_format(u"192.168.1.1") + self.assertEqual(u'192.168.1.1', result) + + def test_convert_None_address(self): + result = converters.convert_ip_to_canonical_format(None) + self.assertIsNone(result) + + def test_convert_invalid_address(self): + result = converters.convert_ip_to_canonical_format("on") + self.assertEqual("on", result) diff --git a/releasenotes/notes/ipv6_address_usage-ef3d65ad5aa5798b.yaml b/releasenotes/notes/ipv6_address_usage-ef3d65ad5aa5798b.yaml new file mode 100644 index 0000000..a988ce0 --- /dev/null +++ b/releasenotes/notes/ipv6_address_usage-ef3d65ad5aa5798b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + A converter ``convert_ip_to_canonical_format`` has been added to + neutron-lib which allows IPv6 addresses to be stored and + displayed in canonical format. \ No newline at end of file