From 322b328584156e70a67f9c3f9d6a0d618f3cc2e4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 09:46:24 -0500 Subject: [PATCH] Fixes #4211: Include trailing text when naturalizing interface names --- docs/release-notes/version-2.7.md | 8 ++++++++ netbox/utilities/ordering.py | 25 +++++++++++++++---------- netbox/utilities/tests/test_ordering.py | 15 +++++++++++---- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index ef05bda8c..813d81ac2 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,5 +1,12 @@ # v2.7.7 (FUTURE) +**Note:** This release fixes a bug affecting the natural ordering of interfaces. If any interfaces appear unordered in +NetBox, run the following management command to recalculate their naturalized values after upgrading: + +``` +python3 manage.py renaturalize dcim.Interface +``` + ## Enhancements * [#2511](https://github.com/netbox-community/netbox/issues/2511) - Compare object change to the previous change @@ -19,6 +26,7 @@ * [#4196](https://github.com/netbox-community/netbox/issues/4196) - Fix exception when viewing LLDP neighbors page * [#4202](https://github.com/netbox-community/netbox/issues/4202) - Prevent reassignment to master device when bulk editing VC member interfaces * [#4204](https://github.com/netbox-community/netbox/issues/4204) - Fix assignment of mask length when bulk editing prefixes +* [#4211](https://github.com/netbox-community/netbox/issues/4211) - Include trailing text when naturalizing interface names --- diff --git a/netbox/utilities/ordering.py b/netbox/utilities/ordering.py index 39e21733a..346a99488 100644 --- a/netbox/utilities/ordering.py +++ b/netbox/utilities/ordering.py @@ -7,7 +7,8 @@ INTERFACE_NAME_REGEX = r'(^(?P[^\d\.:]+)?)' \ r'((?P\d+)/)?' \ r'((?P\d+))?' \ r'(:(?P\d+))?' \ - r'(.(?P\d+)$)?' + r'(\.(?P\d+))?' \ + r'(?P.*)$' def naturalize(value, max_length, integer_places=8): @@ -50,7 +51,7 @@ def naturalize_interface(value, max_length): :param value: The value to be naturalized :param max_length: The maximum length of the returned string. Characters beyond this length will be stripped. """ - output = [] + output = '' match = re.search(INTERFACE_NAME_REGEX, value) if match is None: return value @@ -60,21 +61,25 @@ def naturalize_interface(value, max_length): for part_name in ('slot', 'subslot', 'position', 'subposition'): part = match.group(part_name) if part is not None: - output.append(part.rjust(4, '0')) + output += part.rjust(4, '0') else: - output.append('9999') + output += '9999' # Append the type, if any. if match.group('type') is not None: - output.append(match.group('type')) + output += match.group('type') - # Finally, append any remaining fields, left-padding to six digits each. + # Append any remaining fields, left-padding to six digits each. for part_name in ('id', 'channel', 'vc'): part = match.group(part_name) if part is not None: - output.append(part.rjust(6, '0')) + output += part.rjust(6, '0') else: - output.append('000000') + output += '000000' - ret = ''.join(output) - return ret[:max_length] + # Finally, naturalize any remaining text and append it + if match.group('remainder') is not None and len(output) < max_length: + remainder = naturalize(match.group('remainder'), max_length - len(output)) + output += remainder + + return output[:max_length] diff --git a/netbox/utilities/tests/test_ordering.py b/netbox/utilities/tests/test_ordering.py index 958224c94..30692f46a 100644 --- a/netbox/utilities/tests/test_ordering.py +++ b/netbox/utilities/tests/test_ordering.py @@ -9,8 +9,8 @@ class NaturalizationTestCase(TestCase): """ def test_naturalize(self): + # Original, naturalized data = ( - # Original, naturalized ('abc', 'abc'), ('123', '00000123'), ('abc123', 'abc00000123'), @@ -21,15 +21,16 @@ class NaturalizationTestCase(TestCase): ) for origin, naturalized in data: - self.assertEqual(naturalize(origin, max_length=50), naturalized) + self.assertEqual(naturalize(origin, max_length=100), naturalized) def test_naturalize_max_length(self): self.assertEqual(naturalize('abc123def456', max_length=10), 'abc0000012') def test_naturalize_interface(self): + # Original, naturalized data = ( - # Original, naturalized + # IOS/JunOS-style ('Gi', '9999999999999999Gi000000000000000000'), ('Gi1', '9999999999999999Gi000001000000000000'), ('Gi1/2', '0001999999999999Gi000002000000000000'), @@ -40,10 +41,16 @@ class NaturalizationTestCase(TestCase): ('Gi1/2/3/4/5:6.7', '0001000200030004Gi000005000006000007'), ('Gi1:2', '9999999999999999Gi000001000002000000'), ('Gi1:2.3', '9999999999999999Gi000001000002000003'), + # Generic + ('Interface 1', '9999999999999999Interface 000001000000000000'), + ('Interface 1 (other)', '9999999999999999Interface 000001000000000000 (other)'), + ('Interface 99', '9999999999999999Interface 000099000000000000'), + ('PCIe1-p1', '9999999999999999PCIe000001000000000000-p00000001'), + ('PCIe1-p99', '9999999999999999PCIe000001000000000000-p00000099'), ) for origin, naturalized in data: - self.assertEqual(naturalize_interface(origin, max_length=50), naturalized) + self.assertEqual(naturalize_interface(origin, max_length=100), naturalized) def test_naturalize_interface_max_length(self): self.assertEqual(naturalize_interface('Gi1/2/3', max_length=20), '0001000299999999Gi00')