From c64d273084bcd3d43c6b63dc070de3f244f86ca8 Mon Sep 17 00:00:00 2001 From: Matthew Oliver Date: Thu, 2 Jul 2020 18:21:53 +1000 Subject: [PATCH] cephadm: Make list_networks ipv6 enabled Currently the list_network command and methods in cephadm only run and parse ipv4 output from `ip route`. This patch extends the list_network command and internal methods to be ipv6 enabled. It now also checks `ip -6 route` and `ip -6 addr` to gather gather all networks from both protocol families. Signed-off-by: Matthew Oliver --- src/cephadm/cephadm | 42 ++++++++++++++++++++-- src/cephadm/tests/test_cephadm.py | 59 +++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/cephadm/cephadm b/src/cephadm/cephadm index 1a62a61f639..dad1a361f2f 100755 --- a/src/cephadm/cephadm +++ b/src/cephadm/cephadm @@ -2418,7 +2418,8 @@ def command_bootstrap(): # make sure IP is configured locally, and then figure out the # CIDR network for net, ips in list_networks().items(): - if base_ip in ips: + if ipaddress.ip_address(unicode(base_ip)) in \ + [ipaddress.ip_address(unicode(ip)) for ip in ips]: mon_network = net logger.info('Mon IP %s is in CIDR network %s' % (base_ip, mon_network)) @@ -3121,10 +3122,15 @@ def list_networks(): #j = json.loads(out) #for x in j: + res = _list_ipv4_networks() + res.update(_list_ipv6_networks()) + return res + +def _list_ipv4_networks(): out, _, _ = call_throws([find_executable('ip'), 'route', 'ls']) - return _parse_ip_route(out) + return _parse_ipv4_route(out) -def _parse_ip_route(out): +def _parse_ipv4_route(out): r = {} # type: Dict[str,List[str]] p = re.compile(r'^(\S+) (.*)scope link (.*)src (\S+)') for line in out.splitlines(): @@ -3138,6 +3144,36 @@ def _parse_ip_route(out): r[net].append(ip) return r +def _list_ipv6_networks(): + routes, _, _ = call_throws([find_executable('ip'), '-6', 'route', 'ls']) + ips, _, _ = call_throws([find_executable('ip'), '-6', 'addr', 'ls']) + return _parse_ipv6_route(routes, ips) + +def _parse_ipv6_route(routes, ips): + r = {} # type: Dict[str,List[str]] + route_p = re.compile(r'^(\S+) dev (\S+) proto (\S+) metric (\S+) pref (\S+)$') + ip_p = re.compile(r'^\s+inet6 (\S+)/(.*)scope (.*)$') + for line in routes.splitlines(): + m = route_p.findall(line) + if not m or m[0][0].lower() == 'default': + continue + net = m[0][0] + if net not in r: + r[net] = [] + + for line in ips.splitlines(): + m = ip_p.findall(line) + if not m: + continue + ip = m[0][0] + # find the network it belongs to + net = [n for n in r.keys() + if ipaddress.ip_address(unicode(ip)) in ipaddress.ip_network(unicode(n))] + if net: + r[net[0]].append(ip) + + return r + def command_list_networks(): # type: () -> None r = list_networks() diff --git a/src/cephadm/tests/test_cephadm.py b/src/cephadm/tests/test_cephadm.py index b44f0b72219..bea83d71a65 100644 --- a/src/cephadm/tests/test_cephadm.py +++ b/src/cephadm/tests/test_cephadm.py @@ -90,8 +90,63 @@ default via 10.3.64.1 dev eno1 proto static metric 100 '192.168.122.0/24': ['192.168.122.1']} ), ]) - def test_parse_ip_route(self, test_input, expected): - assert cd._parse_ip_route(test_input) == expected + def test_parse_ipv4_route(self, test_input, expected): + assert cd._parse_ipv4_route(test_input) == expected + + @pytest.mark.parametrize("test_routes, test_ips, expected", [ + ( +""" +::1 dev lo proto kernel metric 256 pref medium +fdbc:7574:21fe:9200::/64 dev wlp2s0 proto ra metric 600 pref medium +fdd8:591e:4969:6363::/64 dev wlp2s0 proto ra metric 600 pref medium +fe80::/64 dev tun0 proto kernel metric 256 pref medium +fe80::/64 dev wlp2s0 proto kernel metric 600 pref medium +default dev tun0 proto static metric 50 pref medium +default via fe80::2480:28ec:5097:3fe2 dev wlp2s0 proto ra metric 20600 pref medium +""", +""" +1: lo: mtu 65536 state UNKNOWN qlen 1000 + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eth0: mtu 1500 state UP qlen 1000 + inet6 fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic + valid_lft 86394sec preferred_lft 14394sec + inet6 fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic + valid_lft 6745sec preferred_lft 3145sec + inet6 fdd8:591e:4969:6363:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic + valid_lft 86394sec preferred_lft 0sec + inet6 fdbc:7574:21fe:9200:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic + valid_lft 6745sec preferred_lft 0sec + inet6 fdd8:591e:4969:6363:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic + valid_lft 86394sec preferred_lft 0sec + inet6 fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic + valid_lft 6745sec preferred_lft 0sec + inet6 fdd8:591e:4969:6363:d581:4321:380b:3905/64 scope global temporary deprecated dynamic + valid_lft 86394sec preferred_lft 0sec + inet6 fdbc:7574:21fe:9200:d581:4321:380b:3905/64 scope global temporary deprecated dynamic + valid_lft 6745sec preferred_lft 0sec + inet6 fe80::1111:2222:3333:4444/64 scope link noprefixroute + valid_lft forever preferred_lft forever +12: tun0: mtu 1500 state UNKNOWN qlen 100 + inet6 fe80::cafe:cafe:cafe:cafe/64 scope link stable-privacy + valid_lft forever preferred_lft forever +""", + { + "::1": ["::1"], + "fdbc:7574:21fe:9200::/64": ["fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4", + "fdbc:7574:21fe:9200:103a:abcd:af1f:57f3", + "fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f", + "fdbc:7574:21fe:9200:d581:4321:380b:3905"], + "fdd8:591e:4969:6363::/64": ["fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4", + "fdd8:591e:4969:6363:103a:abcd:af1f:57f3", + "fdd8:591e:4969:6363:a128:1234:2bdd:1b6f", + "fdd8:591e:4969:6363:d581:4321:380b:3905"], + "fe80::/64": ["fe80::1111:2222:3333:4444", + "fe80::cafe:cafe:cafe:cafe"] + } + )]) + def test_parse_ipv6_route(self, test_routes, test_ips, expected): + assert cd._parse_ipv6_route(test_routes, test_ips) == expected def test_is_ipv6(self): cd.logger = mock.Mock() -- 2.47.3