From 223ca16a81ce337fd344dfbcb2c0acc5f668eb60 Mon Sep 17 00:00:00 2001 From: Wen Liang Date: Wed, 17 Mar 2021 11:48:41 -0400 Subject: [PATCH] Support specifying mac address without quotation mark In yaml, all the string value consists of a series of one-digit or two-digit numbers delimited by colons and all numbers but the first are between 0 and 59, it would be interpreted as a sexagesimal number, and would be converted automatically to the equivalent number of seconds. The committed patch will guarantee to remediate the yaml's peculiarity and convert back into the mac address specified in the original yaml file. And in case that user specifies arbitrary integer(e.g. `mac:1234`), we introduce `force_len`(e.g. `MAC_ADDR_OCTETS = [6, 20]`) to raise error for invalid integer value. Signed-off-by: Wen Liang --- .../network_lsr/argument_validator.py | 19 +++++++++- module_utils/network_lsr/utils.py | 20 ++++++++++- tests/unit/test_network_connections.py | 35 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index 1bfaeda5e..53e312ccd 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -11,6 +11,9 @@ from ansible.module_utils.network_lsr.utils import Util # noqa:E501 UINT32_MAX = 0xFFFFFFFF +# a MAC address for type ethernet requires 6 octets +# a MAC address for type infiniband requires 20 octets +MAC_ADDR_OCTETS = [6, 20] class ArgUtil: @@ -413,11 +416,25 @@ def _validate_impl(self, value, name): class ArgValidatorMac(ArgValidatorStr): - def __init__(self, name, force_len=None, required=False, default_value=None): + def __init__( + self, name, force_len=MAC_ADDR_OCTETS, required=False, default_value=None + ): ArgValidatorStr.__init__(self, name, required, default_value, None) self.force_len = force_len def _validate_impl(self, value, name): + if type(value) == int: + try: + value = Util.num_to_mac(value, self.force_len) + except MyError: + raise ValidationError( + name, "value '%s' is not a valid MAC address" % (value) + ) + if not isinstance(value, Util.STRING_TYPE): + raise ValidationError( + name, + "must be a string and value should be quoted, but is '%s', " % (value), + ) v = ArgValidatorStr._validate_impl(self, value, name) try: addr = Util.mac_aton(v, self.force_len) diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index 73d9528bb..7be6ea33e 100644 --- a/module_utils/network_lsr/utils.py +++ b/module_utils/network_lsr/utils.py @@ -250,7 +250,7 @@ def mac_aton(mac_str, force_len=None): if i == 1: raise MyError("not a valid MAC address: '%s'" % (mac_str)) if force_len is not None: - if force_len != len(b): + if not any(a for a in force_len if a == len(b)): raise MyError( "not a valid MAC address of length %s: '%s'" % (force_len, mac_str) ) @@ -267,6 +267,24 @@ def mac_ntoa(mac): def mac_norm(mac_str, force_len=None): return Util.mac_ntoa(Util.mac_aton(mac_str, force_len)) + @staticmethod + def num_to_mac(num, force_len=None): + mac = [] + s_num = num + while num: + num, mod = divmod(num, 60) + if mod < 10: + mac.insert(0, "0" + str(mod)) + else: + mac.insert(0, str(mod)) + mac_addr = ":".join(mac) + if force_len is not None: + if not any(a for a in force_len if a == len(mac)): + raise MyError( + "not a valid number '%d' for MAC address: '%s'" % (s_num, mac_addr) + ) + return mac_addr + @staticmethod def boolean(arg): if arg is None or isinstance(arg, bool): diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 847f18f26..5563ecd87 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -3260,6 +3260,41 @@ def test_set_deprecated_slave_type(self): self.assertTrue("port_type" in connection) self.assertTrue("slave_type" not in connection) + def test_mac_address_argvalidator(self): + """ + Test that argvalidator for validating mac address is correctly defined. + """ + validator = network_lsr.argument_validator.ArgValidatorMac("mac") + self.assertEqual(validator.validate(41135085296), "52:54:00:12:34:56") + self.assertEqual(validator.validate("00:00:5e:00:53:5d"), "00:00:5e:00:53:5d") + self.assertEqual(validator.validate("00:00:00:00:00:00"), "00:00:00:00:00:00") + self.assertEqual(validator.validate("ff:ff:ff:ff:ff:ff"), "ff:ff:ff:ff:ff:ff") + self.assertEqual( + validator.validate( + "80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:00:70:33:cf:01" + ), + "80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:00:70:33:cf:01", + ) + self.assertEqual( + validator.validate( + "01:02:03:04:05:06:07:08:09:0A:01:02:03:04:05:06:07:08:09:11" + ), + "01:02:03:04:05:06:07:08:09:0a:01:02:03:04:05:06:07:08:09:11", + ) + self.assertValidationError(validator, 1234) + self.assertValidationError(validator, "aa:bb") + self.assertValidationError(validator, sys.maxsize) + self.assertValidationError(validator, 0) + self.assertValidationError( + validator, "80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:00:70:33:cf:" + ) + self.assertValidationError( + validator, "80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:00:70:33:cf:xx" + ) + self.assertValidationError( + validator, "80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:000:70:33:cf:01" + ) + @my_test_skipIf(nmutil is None, "no support for NM (libnm via pygobject)") class TestNM(unittest.TestCase):