Browse Source

Add possibility to run dhcp discover without VLANS

DHCP discover check should be performed without regarding of tagged
interfaces (as they are not present on bootstrapped nodes), thus now by
default vlans are not considered. Special flag was added
to corresponding command that toggles such extended check.

Also all network interfaces (except admin) on bootstrapped nodes are
down at the moment the check is performed and thus must be UP-ed for the
time it takes. Helper class utils.IfaceState modified to work with
multiple interfaces now.

Several commands of dhcpchecker were renamed to reflect its actual purpose

Change-Id: I30e4c1614095291bf9a5cb144f15800d1bd6f850
Closes-Bug: #1569325
Artem Roma 2 years ago
parent
commit
6505d38c66

+ 27
- 9
dhcp_checker/api.py View File

@@ -107,24 +107,42 @@ def make_listeners(ifaces):
107 107
 
108 108
 
109 109
 @utils.filter_duplicated_results
110
-def check_dhcp_with_vlans(config, timeout=5, repeat=2):
110
+def check_dhcp_with_vlans(config, timeout=5, repeat=2, w_vlans=True):
111 111
     """Provide config of {iface: [vlans..]} pairs
112 112
 
113 113
     @config - {'eth0': (100, 101), 'eth1': (100, 102)}
114
+
115
+    'w_vlans' flag toggles dhcp discover check for tagged interfaces;
116
+    if it is False - the request will be sent only for real interfaces
117
+
118
+    Before the request is sent interfaces are set (if they haven't been before)
119
+    and after - down-ed (only those up-ed for the purpose of this checking)
120
+
121
+    If the flag 'w_vlans' holds True but list of vlans for iface is [0],
122
+    the check will also be performed only for real interfaces (see logic of
123
+    utils.VlansContext context manager)
114 124
     """
115 125
     # vifaces - list of pairs ('eth0', ['eth0.100', 'eth0.101'])
116 126
     with utils.VlansContext(config) as vifaces:
117 127
         ifaces, vlans = zip(*vifaces)
118
-        listeners = make_listeners(ifaces)
119 128
 
120
-        for _ in xrange(repeat):
121
-            for i in utils.filtered_ifaces(itertools.chain(ifaces, *vlans)):
122
-                send_dhcp_discover(i)
123
-            time.sleep(timeout)
129
+        # up interfaces before making the check
130
+        with utils.IfaceState(ifaces) as rdy_ifaces:
131
+            listeners = make_listeners(rdy_ifaces)
132
+
133
+            for _ in xrange(repeat):
134
+                ifaces_to_check = (
135
+                    itertools.chain(rdy_ifaces, *vlans)
136
+                    if w_vlans else rdy_ifaces
137
+                )
138
+
139
+                for i in utils.filtered_ifaces(ifaces_to_check):
140
+                    send_dhcp_discover(i)
141
+                time.sleep(timeout)
124 142
 
125
-        for l in listeners:
126
-            for pkt in l.readpkts():
127
-                yield utils.format_answer(scapy.Ether(pkt[1]), l.name)
143
+            for l in listeners:
144
+                for pkt in l.readpkts():
145
+                    yield utils.format_answer(scapy.Ether(pkt[1]), l.name)
128 146
 
129 147
 
130 148
 @utils.single_format

+ 6
- 3
dhcp_checker/commands.py View File

@@ -92,22 +92,25 @@ class ListDhcpAssignment(lister.Lister, BaseCommand):
92 92
         return columns, [first.values()] + [item.values() for item in res]
93 93
 
94 94
 
95
-class DhcpWithVlansCheck(lister.Lister, BaseCommand):
95
+class DhcpCheckDiscover(lister.Lister, BaseCommand):
96 96
     """Provide iface with list of vlans to check
97 97
 
98 98
     If no vlans created - they will be. After creation they won't be deleted.
99 99
     """
100 100
 
101 101
     def get_parser(self, prog_name):
102
-        parser = super(DhcpWithVlansCheck, self).get_parser(prog_name)
102
+        parser = super(DhcpCheckDiscover, self).get_parser(prog_name)
103 103
         parser.add_argument('config',
104 104
                             help='Ethernet interface name')
105
+        parser.add_argument('--with-vlans', action='store_true',
106
+                            help='Enable the check for tagged ifaces')
105 107
         return parser
106 108
 
107 109
     def take_action(self, parsed_args):
108 110
         res = list(api.check_dhcp_with_vlans(json.loads(parsed_args.config),
109 111
                                              timeout=parsed_args.timeout,
110
-                                             repeat=parsed_args.repeat))
112
+                                             repeat=parsed_args.repeat,
113
+                                             w_vlans=parsed_args.with_vlans))
111 114
         if not res:
112 115
             res = [{}]
113 116
         return (utils.DHCP_OFFER_COLUMNS,

+ 9
- 9
dhcp_checker/tests/system/tests.py View File

@@ -70,9 +70,9 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
70 70
 
71 71
     def test_dhcp_server_on_eth2_down(self):
72 72
         """iface should be ifuped in case it's down and rolledback after"""
73
-        manager = utils.IfaceState(self.iface_down)
74
-        with manager as iface:
75
-            response = api.check_dhcp_on_eth(iface, 2)
73
+        manager = utils.IfaceState([self.iface_down])
74
+        with manager as ifaces:
75
+            response = api.check_dhcp_on_eth(ifaces[0], 2)
76 76
 
77 77
         self.assertEqual(len(response), 1)
78 78
         self.assertTrue(response[0]['server_ip'])
@@ -82,9 +82,9 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
82 82
 
83 83
     def test_dhcp_server_on_eth0_up(self):
84 84
         """Test verifies that if iface is up, it won't be touched"""
85
-        manager = utils.IfaceState(self.iface_up)
86
-        with manager as iface:
87
-            response = api.check_dhcp_on_eth(iface, 2)
85
+        manager = utils.IfaceState([self.iface_up])
86
+        with manager as ifaces:
87
+            response = api.check_dhcp_on_eth(ifaces[0], 2)
88 88
 
89 89
         self.assertEqual(len(response), 1)
90 90
         self.assertTrue(response[0]['server_ip'])
@@ -95,9 +95,9 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
95 95
     def test_dhcp_server_on_nonexistent_iface(self):
96 96
 
97 97
         def test_check():
98
-            manager = utils.IfaceState('eth10')
99
-            with manager as iface:
100
-                api.check_dhcp_on_eth(iface, 2)
98
+            manager = utils.IfaceState(['eth10'])
99
+            with manager as ifaces:
100
+                api.check_dhcp_on_eth(ifaces[0], 2)
101 101
         self.assertRaises(EnvironmentError, test_check)
102 102
 
103 103
     def tearDown(self):

+ 53
- 13
dhcp_checker/tests/unit/test_api.py View File

@@ -45,6 +45,23 @@ expected_response = {
45 45
 }
46 46
 
47 47
 
48
+class IfaceStateMock(object):
49
+
50
+    def __init__(self, ifaces):
51
+        self.ifaces = ifaces
52
+
53
+    def __enter__(self):
54
+        return self.ifaces
55
+
56
+    def __exit__(self, exc_type, exc_val, ex_tb):
57
+        pass
58
+
59
+
60
+def filtered_ifaces_mock(ifaces):
61
+    return ifaces
62
+
63
+
64
+@patch('dhcp_checker.utils.IfaceState', new=IfaceStateMock)
48 65
 class TestDhcpApi(unittest.TestCase):
49 66
 
50 67
     def setUp(self):
@@ -53,9 +70,14 @@ class TestDhcpApi(unittest.TestCase):
53 70
                                                          'dhcp.pcap')))
54 71
         self.dhcp_response = self.scapy_data[1:]
55 72
 
73
+        self.config_sample = {
74
+            'eth0': (100, 101),
75
+            'eth1': (100, 102)
76
+        }
77
+
56 78
     @patch('dhcp_checker.api.scapy.srp')
57 79
     @patch('dhcp_checker.api.scapy.get_if_raw_hwaddr')
58
-    def test_check_dhcp_on_eth(self, raw_hwaddr, srp_mock):
80
+    def test_check_dhcp_on_eth(self, raw_hwaddr, srp_mock, *_):
59 81
         raw_hwaddr.return_value = ('111', '222')
60 82
         srp_mock.return_value = ([self.dhcp_response], [])
61 83
         response = api.check_dhcp_on_eth('eth1', timeout=5)
@@ -63,36 +85,53 @@ class TestDhcpApi(unittest.TestCase):
63 85
 
64 86
     @patch('dhcp_checker.api.scapy.srp')
65 87
     @patch('dhcp_checker.api.scapy.get_if_raw_hwaddr')
66
-    def test_check_dhcp_on_eth_empty_response(self, raw_hwaddr, srp_mock):
88
+    def test_check_dhcp_on_eth_empty_response(self, raw_hwaddr, srp_mock, *_):
67 89
         raw_hwaddr.return_value = ('111', '222')
68 90
         srp_mock.return_value = ([], [])
69 91
         response = api.check_dhcp_on_eth('eth1', timeout=5)
70 92
         self.assertEqual([], response)
71 93
 
94
+    @patch('dhcp_checker.utils.filtered_ifaces')
72 95
     @patch('dhcp_checker.api.send_dhcp_discover')
73 96
     @patch('dhcp_checker.api.make_listeners')
74 97
     def test_check_dhcp_with_multiple_ifaces(
75
-            self, make_listeners, send_discover):
76
-        api.check_dhcp(['eth1', 'eth2'], repeat=1)
98
+            self, make_listeners, send_discover, filtered_ifaces, *_):
99
+        repeat = 1
100
+        ifaces = ['eth1', 'eth2']
101
+
102
+        filtered_ifaces.return_value = ifaces
103
+
104
+        api.check_dhcp(ifaces, repeat=repeat)
105
+
77 106
         make_listeners.assert_called_once_with(('eth2', 'eth1'))
107
+        self.assertEqual(filtered_ifaces.call_count, repeat)
78 108
         self.assertEqual(send_discover.call_count, 2)
79 109
 
110
+    @patch('dhcp_checker.utils.filtered_ifaces', new=filtered_ifaces_mock)
80 111
     @patch('dhcp_checker.api.send_dhcp_discover')
81 112
     @patch('dhcp_checker.api.make_listeners')
82
-    def test_check_dhcp_with_vlans(self, make_listeners, send_discover):
83
-        config_sample = {
84
-            'eth0': (100, 101),
85
-            'eth1': (100, 102)
86
-        }
87
-        api.check_dhcp_with_vlans(config_sample, timeout=1)
113
+    def test_check_dhcp_with_vlans(self, make_listeners, send_discover, *_):
114
+
115
+        api.check_dhcp_with_vlans(self.config_sample, timeout=1, repeat=1,
116
+                                  w_vlans=True)
88 117
         make_listeners.assert_called_once_with(('eth1', 'eth0'))
89 118
         self.assertEqual(send_discover.call_count, 6)
90 119
 
120
+    @patch('dhcp_checker.utils.filtered_ifaces', new=filtered_ifaces_mock)
121
+    @patch('dhcp_checker.api.send_dhcp_discover')
122
+    @patch('dhcp_checker.api.make_listeners')
123
+    def test_check_dhcp_wo_vlans(self, make_listeners, send_discover, *_):
124
+        api.check_dhcp_with_vlans(self.config_sample, timeout=1, repeat=1,
125
+                                  w_vlans=False)
126
+        make_listeners.assert_called_once_with(('eth1', 'eth0'))
127
+        self.assertEqual(send_discover.call_count, 2)
128
+
129
+    @patch('dhcp_checker.utils.filtered_ifaces', new=filtered_ifaces_mock)
91 130
     @patch('dhcp_checker.api.time.sleep')
92 131
     @patch('dhcp_checker.api.send_dhcp_discover')
93 132
     @patch('dhcp_checker.api.make_listeners')
94 133
     def test_check_dhcp_with_vlans_repeat_2(self, make_listeners,
95
-                                            send_discover, sleep_mock):
134
+                                            send_discover, sleep_mock, *_):
96 135
         config_sample = {
97 136
             'eth0': (),
98 137
         }
@@ -101,12 +140,13 @@ class TestDhcpApi(unittest.TestCase):
101 140
         make_listeners.assert_called_once_with(('eth0',))
102 141
         self.assertEqual(send_discover.call_count, 3)
103 142
 
104
-    @patch('dhcp_checker.api.utils.filtered_ifaces')
143
+    @patch('dhcp_checker.utils.filtered_ifaces')
105 144
     @patch('dhcp_checker.api.get_ifaces_exclude_lo')
106 145
     @patch('dhcp_checker.api.send_dhcp_discover')
107 146
     @patch('dhcp_checker.api.make_listeners')
108 147
     def test_check_dhcp_with_no_ifaces(
109
-            self, make_listeners, send_discover, interfaces, filtered_ifaces):
148
+            self, make_listeners, send_discover, interfaces,
149
+            filtered_ifaces, *_):
110 150
         interfaces.return_value = ['eth1']
111 151
         filtered_ifaces.return_value = ['eth1']
112 152
         api.check_dhcp(None, timeout=1, repeat=2)

+ 4
- 3
dhcp_checker/tests/unit/test_commands.py View File

@@ -37,7 +37,7 @@ class TestCommandsInterface(unittest.TestCase):
37 37
 
38 38
     def test_list_dhcp_servers(self, api):
39 39
         api.check_dhcp.return_value = iter([expected_response])
40
-        command = cli.main(['discover', '--ifaces', 'eth0', 'eth1',
40
+        command = cli.main(['listservers', '--ifaces', 'eth0', 'eth1',
41 41
                             '--format', 'json'])
42 42
         self.assertEqual(command, 0)
43 43
         api.check_dhcp.assert_called_once_with(['eth0', 'eth1'],
@@ -57,7 +57,8 @@ class TestCommandsInterface(unittest.TestCase):
57 57
         config_sample = {'eth1': ['100', '101'],
58 58
                          'eth2': range(103, 110)}
59 59
         api.check_dhcp_with_vlans.return_value = iter([expected_response])
60
-        command = cli.main(['vlans', json.dumps(config_sample)])
60
+        command = cli.main(['discover', json.dumps(config_sample),
61
+                            '--with-vlans'])
61 62
         self.assertEqual(command, 0)
62 63
         api.check_dhcp_with_vlans.assert_called_once_with(
63
-            config_sample, repeat=2, timeout=5)
64
+            config_sample, repeat=2, timeout=5, w_vlans=True)

+ 6
- 6
dhcp_checker/tests/unit/test_utils.py View File

@@ -156,16 +156,16 @@ class TestIfaceStateHelper(unittest.TestCase):
156 156
     def test_iface_is_up(self, command, iface_state):
157 157
         iface_value = iter(('UP',) * 3)
158 158
         iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
159
-        with utils.IfaceState('eth1') as iface:
160
-            self.assertEqual(iface, 'eth1')
159
+        with utils.IfaceState(['eth1']) as ifaces:
160
+            self.assertEqual(ifaces[0], 'eth1')
161 161
         self.assertEqual(iface_state.call_count, 2)
162 162
         self.assertEqual(command.call_count, 0)
163 163
 
164 164
     def test_iface_is_down(self, command, iface_state):
165 165
         iface_value = iter(('DOWN', 'UP', 'DOWN'))
166 166
         iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
167
-        with utils.IfaceState('eth1') as iface:
168
-            self.assertEqual(iface, 'eth1')
167
+        with utils.IfaceState(['eth1']) as ifaces:
168
+            self.assertEqual(ifaces[0], 'eth1')
169 169
         self.assertEqual(iface_state.call_count, 3)
170 170
         self.assertEqual(command.call_count, 2)
171 171
         self.assertEqual(command.call_args_list,
@@ -177,7 +177,7 @@ class TestIfaceStateHelper(unittest.TestCase):
177 177
         iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
178 178
 
179 179
         def test_raises():
180
-            with utils.IfaceState('eth1', retry=4) as iface:
181
-                self.assertEqual(iface, 'eth1')
180
+            with utils.IfaceState(['eth1'], retry=4) as ifaces:
181
+                self.assertEqual(ifaces[0], 'eth1')
182 182
         self.assertRaises(EnvironmentError, test_raises)
183 183
         self.assertEqual(command.call_count, 4)

+ 27
- 18
dhcp_checker/utils.py View File

@@ -14,6 +14,7 @@
14 14
 #    with this program; if not, write to the Free Software Foundation, Inc.,
15 15
 #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 16
 
17
+import copy
17 18
 import functools
18 19
 import re
19 20
 import subprocess
@@ -207,33 +208,41 @@ class VlansContext(object):
207 208
 
208 209
 
209 210
 class IfaceState(object):
210
-    """Context manager to control state of iface while dhcp checker runs"""
211
+    """Context manager to control state of ifaces while dhcp checker runs"""
211 212
 
212
-    def __init__(self, iface, rollback=True, retry=3):
213
+    def __init__(self, ifaces, rollback=True, retry=3):
213 214
         self.rollback = rollback
214 215
         self.retry = retry
215
-        self.iface = iface
216
-        self.pre_iface_state = _iface_state(iface)
217
-        self.iface_state = self.pre_iface_state
218
-        self.post_iface_state = ''
219
-
220
-    def iface_up(self):
221
-        while self.retry and self.iface_state != 'UP':
222
-            command_util('ifconfig', self.iface, 'up')
223
-            self.iface_state = _iface_state(self.iface)
216
+        self.ifaces = ifaces
217
+        self.pre_ifaces_state = self.get_ifaces_state()
218
+        self.ifaces_state = copy.deepcopy(self.pre_ifaces_state)
219
+        self.post_ifaces_state = {}
220
+
221
+    def get_ifaces_state(self):
222
+        state = {}
223
+        for iface in self.ifaces:
224
+            state[iface] = _iface_state(iface)
225
+        return state
226
+
227
+    def iface_up(self, iface):
228
+        while self.retry and self.ifaces_state[iface] != 'UP':
229
+            command_util('ifconfig', iface, 'up')
230
+            self.ifaces_state[iface] = _iface_state(iface)
224 231
             self.retry -= 1
225
-        if self.iface_state != 'UP':
232
+        if self.ifaces_state[iface] != 'UP':
226 233
             raise EnvironmentError(
227
-                'Tried my best to ifup iface {0}.'.format(self.iface))
234
+                'Tried my best to ifup iface {0}.'.format(iface))
228 235
 
229 236
     def __enter__(self):
230
-        self.iface_up()
231
-        return self.iface
237
+        for iface in self.ifaces:
238
+            self.iface_up(iface)
239
+        return self.ifaces
232 240
 
233 241
     def __exit__(self, exc_type, exc_val, exc_tb):
234
-        if self.pre_iface_state != 'UP' and self.rollback:
235
-            command_util('ifconfig', self.iface, 'down')
236
-        self.post_iface_state = _iface_state(self.iface)
242
+        for iface in self.ifaces:
243
+            if self.pre_ifaces_state[iface] != 'UP' and self.rollback:
244
+                command_util('ifconfig', iface, 'down')
245
+        self.post_ifaces_state[iface] = _iface_state(iface)
237 246
 
238 247
 
239 248
 def create_mac_filter(iface):

+ 2
- 2
setup.py View File

@@ -39,9 +39,9 @@ setuptools.setup(
39 39
             'urlaccesscheck = url_access_checker.cli:main',
40 40
         ],
41 41
         'dhcp.check': [
42
-            'discover = dhcp_checker.commands:ListDhcpServers',
42
+            'listservers = dhcp_checker.commands:ListDhcpServers',
43 43
             'request = dhcp_checker.commands:ListDhcpAssignment',
44
-            'vlans = dhcp_checker.commands:DhcpWithVlansCheck'
44
+            'discover = dhcp_checker.commands:DhcpCheckDiscover'
45 45
         ],
46 46
         'network_checker': [
47 47
             'multicast = network_checker.multicast.api:MulticastChecker',

Loading…
Cancel
Save