summaryrefslogtreecommitdiff
path: root/tests/unittests/test_netinfo.py
blob: 238f7b0a5296cf02b5639639105f8f2f26ce73ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# This file is part of cloud-init. See LICENSE file for license information.

"""Tests netinfo module functions and classes."""

from copy import copy

from cloudinit.netinfo import netdev_info, netdev_pformat, route_pformat
from tests.unittests.helpers import CiTestCase, mock, readResource


# Example ifconfig and route output
SAMPLE_OLD_IFCONFIG_OUT = readResource("netinfo/old-ifconfig-output")
SAMPLE_NEW_IFCONFIG_OUT = readResource("netinfo/new-ifconfig-output")
SAMPLE_FREEBSD_IFCONFIG_OUT = readResource("netinfo/freebsd-ifconfig-output")
SAMPLE_IPADDRSHOW_OUT = readResource("netinfo/sample-ipaddrshow-output")
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")
NETDEV_FORMATTED_OUT = readResource("netinfo/netdev-formatted-output")
ROUTE_FORMATTED_OUT = readResource("netinfo/route-formatted-output")
FREEBSD_NETDEV_OUT = readResource("netinfo/freebsd-netdev-formatted-output")


class TestNetInfo(CiTestCase):

    maxDiff = None
    with_logs = True

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_old_nettools_pformat(self, m_subp, m_which):
        """netdev_pformat properly rendering old nettools info."""
        m_subp.return_value = (SAMPLE_OLD_IFCONFIG_OUT, '')
        m_which.side_effect = lambda x: x if x == 'ifconfig' else None
        content = netdev_pformat()
        self.assertEqual(NETDEV_FORMATTED_OUT, content)

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_new_nettools_pformat(self, m_subp, m_which):
        """netdev_pformat properly rendering netdev new nettools info."""
        m_subp.return_value = (SAMPLE_NEW_IFCONFIG_OUT, '')
        m_which.side_effect = lambda x: x if x == 'ifconfig' else None
        content = netdev_pformat()
        self.assertEqual(NETDEV_FORMATTED_OUT, content)

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_freebsd_nettools_pformat(self, m_subp, m_which):
        """netdev_pformat properly rendering netdev new nettools info."""
        m_subp.return_value = (SAMPLE_FREEBSD_IFCONFIG_OUT, '')
        m_which.side_effect = lambda x: x if x == 'ifconfig' else None
        content = netdev_pformat()
        print()
        print(content)
        print()
        self.assertEqual(FREEBSD_NETDEV_OUT, content)

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_iproute_pformat(self, m_subp, m_which):
        """netdev_pformat properly rendering ip route info."""
        m_subp.return_value = (SAMPLE_IPADDRSHOW_OUT, '')
        m_which.side_effect = lambda x: x if x == 'ip' else None
        content = netdev_pformat()
        new_output = copy(NETDEV_FORMATTED_OUT)
        # ip route show describes global scopes on ipv4 addresses
        # whereas ifconfig does not. Add proper global/host scope to output.
        new_output = new_output.replace('|   .    | 50:7b', '| global | 50:7b')
        new_output = new_output.replace(
            '255.0.0.0   |   .    |', '255.0.0.0   |  host  |')
        self.assertEqual(new_output, content)

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_warn_on_missing_commands(self, m_subp, m_which):
        """netdev_pformat warns when missing both ip and 'netstat'."""
        m_which.return_value = None  # Niether ip nor netstat found
        content = netdev_pformat()
        self.assertEqual('\n', content)
        self.assertEqual(
            "WARNING: Could not print networks: missing 'ip' and 'ifconfig'"
            " commands\n",
            self.logs.getvalue())
        m_subp.assert_not_called()

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_info_nettools_down(self, m_subp, m_which):
        """test netdev_info using nettools and down interfaces."""
        m_subp.return_value = (
            readResource("netinfo/new-ifconfig-output-down"), "")
        m_which.side_effect = lambda x: x if x == 'ifconfig' else None
        self.assertEqual(
            {'eth0': {'ipv4': [], 'ipv6': [],
                      'hwaddr': '00:16:3e:de:51:a6', 'up': False},
             'lo': {'ipv4': [{'ip': '127.0.0.1', 'mask': '255.0.0.0'}],
                    'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
                    'hwaddr': '.', 'up': True}},
            netdev_info("."))

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_netdev_info_iproute_down(self, m_subp, m_which):
        """Test netdev_info with ip and down interfaces."""
        m_subp.return_value = (
            readResource("netinfo/sample-ipaddrshow-output-down"), "")
        m_which.side_effect = lambda x: x if x == 'ip' else None
        self.assertEqual(
            {'lo': {'ipv4': [{'ip': '127.0.0.1', 'bcast': '.',
                              'mask': '255.0.0.0', 'scope': 'host'}],
                    'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
                    'hwaddr': '.', 'up': True},
             'eth0': {'ipv4': [], 'ipv6': [],
                      'hwaddr': '00:16:3e:de:51:a6', 'up': False}},
            netdev_info("."))

    @mock.patch('cloudinit.netinfo.netdev_info')
    def test_netdev_pformat_with_down(self, m_netdev_info):
        """test netdev_pformat when netdev_info returns 'down' interfaces."""
        m_netdev_info.return_value = (
            {'lo': {'ipv4': [{'ip': '127.0.0.1', 'mask': '255.0.0.0',
                              'scope': 'host'}],
                    'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
                    'hwaddr': '.', 'up': True},
             'eth0': {'ipv4': [], 'ipv6': [],
                      'hwaddr': '00:16:3e:de:51:a6', 'up': False}})
        self.assertEqual(
            readResource("netinfo/netdev-formatted-output-down"),
            netdev_pformat())

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_route_nettools_pformat(self, m_subp, m_which):
        """route_pformat properly rendering nettools route info."""

        def subp_netstat_route_selector(*args, **kwargs):
            if args[0] == ['netstat', '--route', '--numeric', '--extend']:
                return (SAMPLE_ROUTE_OUT_V4, '')
            if args[0] == ['netstat', '-A', 'inet6', '--route', '--numeric']:
                return (SAMPLE_ROUTE_OUT_V6, '')
            raise Exception('Unexpected subp call %s' % args[0])

        m_subp.side_effect = subp_netstat_route_selector
        m_which.side_effect = lambda x: x if x == 'netstat' else None
        content = route_pformat()
        self.assertEqual(ROUTE_FORMATTED_OUT, content)

    @mock.patch('cloudinit.netinfo.subp.which')
    @mock.patch('cloudinit.netinfo.subp.subp')
    def test_route_iproute_pformat(self, m_subp, m_which):
        """route_pformat properly rendering ip route info."""

        def subp_iproute_selector(*args, **kwargs):
            if ['ip', '-o', 'route', 'list'] == args[0]:
                return (SAMPLE_IPROUTE_OUT_V4, '')
            v6cmd = ['ip', '--oneline', '-6', 'route', 'list', 'table', 'all']
            if v6cmd == args[0]:
                return (SAMPLE_IPROUTE_OUT_V6, '')
            raise Exception('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()
        self.assertEqual(ROUTE_FORMATTED_OUT, 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):
        """route_pformat warns when missing both ip and 'netstat'."""
        m_which.return_value = None  # Niether ip nor netstat found
        content = route_pformat()
        self.assertEqual('\n', content)
        self.assertEqual(
            "WARNING: Could not print routes: missing 'ip' and 'netstat'"
            " commands\n",
            self.logs.getvalue())
        m_subp.assert_not_called()

# vi: ts=4 expandtab