From 7c52b8a5fbcfe1e7442bbd011956b52e303745c5 Mon Sep 17 00:00:00 2001 From: Dmitry Zykov <44090270+zykovd@users.noreply.github.com> Date: Fri, 12 May 2023 21:48:10 +0300 Subject: Fix KeyError in iproute pformat (#3287) This fixes KeyError on specific network configuration when running cloud-init on "network" stage. The same problem was mentioned in #746 and #1041. --- cloudinit/netinfo.py | 44 ++++++++++++---------- .../netinfo/route-formatted-output-missing-gateway | 19 ++++++++++ .../sample-iproute-output-v4-missing-gateway | 1 + .../sample-iproute-output-v6-missing-gateway | 10 +++++ tests/unittests/test_netinfo.py | 27 +++++++++++++ tools/.github-cla-signers | 1 + 6 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 tests/data/netinfo/route-formatted-output-missing-gateway create mode 100644 tests/data/netinfo/sample-iproute-output-v4-missing-gateway create mode 100644 tests/data/netinfo/sample-iproute-output-v6-missing-gateway diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 0daa27f7..fc25439c 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -598,16 +598,19 @@ def route_pformat(): tbl_v4 = SimpleTable(fields_v4) for (n, r) in enumerate(routes.get("ipv4")): route_id = str(n) - tbl_v4.add_row( - [ - route_id, - r["destination"], - r["gateway"], - r["genmask"], - r["iface"], - r["flags"], - ] - ) + try: + tbl_v4.add_row( + [ + route_id, + r["destination"], + r.get("gateway", "0.0.0.0"), + r["genmask"], + r["iface"], + r["flags"], + ] + ) + except KeyError as e: + util.logexc(LOG, "Route info formatting error: %s" % e) route_s = tbl_v4.get_string() max_len = len(max(route_s.splitlines(), key=len)) header = util.center("Route IPv4 info", "+", max_len) @@ -625,15 +628,18 @@ def route_pformat(): route_id = str(n) if r["iface"] == "lo": continue - tbl_v6.add_row( - [ - route_id, - r["destination"], - r["gateway"], - r["iface"], - r["flags"], - ] - ) + try: + tbl_v6.add_row( + [ + route_id, + r["destination"], + r.get("gateway", "::"), + r["iface"], + r["flags"], + ] + ) + except KeyError as e: + util.logexc(LOG, "Route info formatting error: %s" % e) route_s = tbl_v6.get_string() max_len = len(max(route_s.splitlines(), key=len)) header = util.center("Route IPv6 info", "+", max_len) diff --git a/tests/data/netinfo/route-formatted-output-missing-gateway b/tests/data/netinfo/route-formatted-output-missing-gateway new file mode 100644 index 00000000..8bbd819d --- /dev/null +++ b/tests/data/netinfo/route-formatted-output-missing-gateway @@ -0,0 +1,19 @@ ++++++++++++++++++++++++++++Route IPv4 info+++++++++++++++++++++++++++ ++-------+-------------+---------+---------------+-----------+-------+ +| Route | Destination | Gateway | Genmask | Interface | Flags | ++-------+-------------+---------+---------------+-----------+-------+ +| 0 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | enp0s25 | U | ++-------+-------------+---------+---------------+-----------+-------+ +++++++++++++++++++++++++++Route IPv6 info++++++++++++++++++++++++++ ++-------+---------------------------+---------+-----------+-------+ +| Route | Destination | Gateway | Interface | Flags | ++-------+---------------------------+---------+-----------+-------+ +| 0 | 2a00:abcd:82ae:cd33::657 | :: | enp0s25 | Ue | +| 1 | 2a00:abcd:82ae:cd33::/64 | :: | enp0s25 | U | +| 2 | 2a00:abcd:82ae:cd33::/56 | :: | enp0s25 | U | +| 3 | fd81:123f:654::657 | :: | enp0s25 | U | +| 4 | fd81:123f:654::/64 | :: | enp0s25 | U | +| 5 | fd81:123f:654::/48 | :: | enp0s25 | U | +| 6 | fe80::abcd:ef12:bc34:da21 | :: | enp0s25 | U | +| 7 | fe80::/64 | :: | enp0s25 | U | ++-------+---------------------------+---------+-----------+-------+ diff --git a/tests/data/netinfo/sample-iproute-output-v4-missing-gateway b/tests/data/netinfo/sample-iproute-output-v4-missing-gateway new file mode 100644 index 00000000..c1e0b3c8 --- /dev/null +++ b/tests/data/netinfo/sample-iproute-output-v4-missing-gateway @@ -0,0 +1 @@ +192.168.2.0/24 dev enp0s25 proto kernel scope link src 192.168.2.18 metric 100 diff --git a/tests/data/netinfo/sample-iproute-output-v6-missing-gateway b/tests/data/netinfo/sample-iproute-output-v6-missing-gateway new file mode 100644 index 00000000..ffab1fa7 --- /dev/null +++ b/tests/data/netinfo/sample-iproute-output-v6-missing-gateway @@ -0,0 +1,10 @@ +2a00:abcd:82ae:cd33::657 dev enp0s25 proto kernel metric 256 expires 2334sec pref medium +2a00:abcd:82ae:cd33::/64 dev enp0s25 proto ra metric 100 pref medium +2a00:abcd:82ae:cd33::/56 dev enp0s25 proto ra metric 100 pref medium +fd81:123f:654::657 dev enp0s25 proto kernel metric 256 pref medium +fd81:123f:654::/64 dev enp0s25 proto ra metric 100 pref medium +fd81:123f:654::/48 dev enp0s25 proto ra metric 100 pref medium +fe80::abcd:ef12:bc34:da21 dev enp0s25 proto static metric 100 pref medium +fe80::/64 dev enp0s25 proto kernel metric 256 pref medium +local ::1 dev lo table local proto none metric 0 pref medium +local 2600:1f16:b80:ad00:90a:c915:bca6:5ff2 dev lo table local proto none metric 0 pref medium diff --git a/tests/unittests/test_netinfo.py b/tests/unittests/test_netinfo.py index 7612a28b..49c92f7e 100644 --- a/tests/unittests/test_netinfo.py +++ b/tests/unittests/test_netinfo.py @@ -26,8 +26,17 @@ SAMPLE_ROUTE_OUT_V4 = readResource("netinfo/sample-route-output-v4") SAMPLE_ROUTE_OUT_V6 = readResource("netinfo/sample-route-output-v6") SAMPLE_IPROUTE_OUT_V4 = readResource("netinfo/sample-iproute-output-v4") SAMPLE_IPROUTE_OUT_V6 = readResource("netinfo/sample-iproute-output-v6") +SAMPLE_IPROUTE_OUT_V6_MISSING_GATEWAY = readResource( + "netinfo/sample-iproute-output-v6-missing-gateway" +) +SAMPLE_IPROUTE_OUT_V4_MISSING_GATEWAY = readResource( + "netinfo/sample-iproute-output-v4-missing-gateway" +) NETDEV_FORMATTED_OUT = readResource("netinfo/netdev-formatted-output") ROUTE_FORMATTED_OUT = readResource("netinfo/route-formatted-output") +ROUTE_FORMATTED_OUT_MISSING_GATEWAY = readResource( + "netinfo/route-formatted-output-missing-gateway" +) FREEBSD_NETDEV_OUT = readResource("netinfo/freebsd-netdev-formatted-output") @@ -223,6 +232,24 @@ class TestNetInfo: content = route_pformat() assert ROUTE_FORMATTED_OUT == content + @mock.patch("cloudinit.netinfo.subp.which") + @mock.patch("cloudinit.netinfo.subp.subp") + def test_route_iproute_pformat_missing_gateway(self, m_subp, m_which): + """route_pformat properly rendering info with missing gateway.""" + + def subp_iproute_selector(*args, **kwargs): + if ["ip", "-o", "route", "list"] == args[0]: + return (SAMPLE_IPROUTE_OUT_V4_MISSING_GATEWAY, "") + v6cmd = ["ip", "--oneline", "-6", "route", "list", "table", "all"] + if v6cmd == args[0]: + return (SAMPLE_IPROUTE_OUT_V6_MISSING_GATEWAY, "") + raise RuntimeError("Unexpected subp call %s" % args[0]) + + m_subp.side_effect = subp_iproute_selector + m_which.side_effect = lambda x: x if x == "ip" else None + content = route_pformat() + assert ROUTE_FORMATTED_OUT_MISSING_GATEWAY == content + @mock.patch("cloudinit.netinfo.subp.which") @mock.patch("cloudinit.netinfo.subp.subp") def test_route_warn_on_missing_commands(self, m_subp, m_which, caplog): diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers index a3d692b6..ab8a9283 100644 --- a/tools/.github-cla-signers +++ b/tools/.github-cla-signers @@ -156,3 +156,4 @@ yangzz-97 yawkat zhan9san zhuzaifangxuele +zykovd -- cgit v1.2.1