Merge ~jocave/plainbox-provider-checkbox:netplan-try-finally into plainbox-provider-checkbox:master

Proposed by Jonathan Cave
Status: Needs review
Proposed branch: ~jocave/plainbox-provider-checkbox:netplan-try-finally
Merge into: plainbox-provider-checkbox:master
Diff against target: 367 lines (+107/-96)
1 file modified
bin/wifi_client_test_netplan.py (+107/-96)
Reviewer Review Type Date Requested Status
Checkbox Developers Pending
Review via email: mp+412678@code.launchpad.net

Description of the change

Changes made when trying to isolate the problem with these tests on the core20 snap 20211124. Needed some kind of persistent logging and ability to attempt to automatically restore the netplan config.

To post a comment you must log in.

Unmerged commits

e411366... by Jonathan Cave

Change: run netplan test in try/finally

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/bin/wifi_client_test_netplan.py b/bin/wifi_client_test_netplan.py
2index d984e2e..21cbb94 100755
3--- a/bin/wifi_client_test_netplan.py
4+++ b/bin/wifi_client_test_netplan.py
5@@ -1,5 +1,5 @@
6 #!/usr/bin/env python3
7-# Copyright 2018-2019 Canonical Ltd.
8+# Copyright 2018-2021 Canonical Ltd.
9 # All rights reserved.
10 #
11 # Written by:
12@@ -14,7 +14,6 @@ To see how to use, please run "./wifi_client_test_netplan.py --help"
13
14 import argparse
15 import datetime
16-import functools
17 import glob
18 import os
19 import subprocess as sp
20@@ -22,18 +21,32 @@ import textwrap
21 import time
22 import shutil
23 import sys
24+import logging
25+import logging.handlers
26
27 from gateway_ping_test import ping
28
29-print = functools.partial(print, flush=True)
30
31+logging.basicConfig(
32+ level=logging.DEBUG,
33+ format='%(message)s',
34+ handlers=[
35+ logging.FileHandler("/var/tmp/checkbox-netplan.log"),
36+ logging.StreamHandler(sys.stdout)
37+ ]
38+)
39
40-def print_head(txt):
41- print("##", txt)
42
43+def info_header(txt):
44+ logging.info("## %s", txt)
45
46-def print_cmd(cmd):
47- print("+", cmd)
48+
49+def info_cmd(cmd):
50+ logging.info("+ %s", cmd)
51+
52+
53+def info_line(line=""):
54+ logging.info("%s", line)
55
56
57 # Configuration file path
58@@ -48,31 +61,31 @@ NETPLAN_TEST_CFG = "/etc/netplan/99-CREATED-BY-CHECKBOX.yaml"
59
60
61 def netplan_config_backup():
62- print_head("Backup any existing netplan configuration files")
63+ info_header("Backup any existing netplan configuration files")
64 if os.path.exists(TMP_PATH):
65- print("Clear backup location")
66+ info_line("Clear backup location")
67 shutil.rmtree(TMP_PATH)
68 for basedir in NETPLAN_CFG_PATHS:
69- print("Checking in {}".format(basedir))
70+ info_line("Checking in {}".format(basedir))
71 files = glob.glob(os.path.join(basedir, '*.yaml'))
72 if files:
73 backup_loc = os.path.join(TMP_PATH, *basedir.split('/'))
74 os.makedirs(backup_loc)
75 for f in files:
76- print(" ", f)
77+ info_line(" {}".format(f))
78 shutil.copy(f, backup_loc)
79- print()
80+ info_line()
81
82
83 def netplan_config_wipe():
84- print_head("Delete any existing netplan configuration files")
85+ info_header("Delete any existing netplan configuration files")
86 # NOTE: this removes not just configs for wifis, but for all device types
87 # (ethernets, bridges) which could be dangerous
88 for basedir in NETPLAN_CFG_PATHS:
89- print("Wiping {}".format(basedir))
90+ info_line("Wiping {}".format(basedir))
91 files = glob.glob(os.path.join(basedir, '*.yaml'))
92 for f in files:
93- print(" ", f)
94+ info_line(" {}".format(f))
95 os.remove(f)
96
97 # If there's any file left in configuration folder then there's something
98@@ -80,22 +93,22 @@ def netplan_config_wipe():
99 for basedir in NETPLAN_CFG_PATHS:
100 files = glob.glob(os.path.join(basedir, "*.yaml"))
101 if files:
102- print("ERROR: Failed to wipe netplan config files:")
103+ logging.critical("ERROR: Failed to wipe netplan config files:")
104 for f in files:
105- print(" ", f)
106+ info_line(" {}".format(f))
107 netplan_config_restore()
108 raise SystemExit("Configuration file restored, exiting...")
109- print()
110+ info_line()
111
112
113 def netplan_config_restore():
114- print_head("Restore configuration files")
115+ info_header("Restore configuration files")
116 files = glob.glob("{}/**/*.yaml".format(TMP_PATH), recursive=True)
117 if files:
118 print("Restoring:")
119 for f in files:
120 restore_loc = f[len(TMP_PATH):]
121- print(" ", restore_loc)
122+ info_line(" {}".format(restore_loc))
123 try:
124 shutil.move(f, restore_loc)
125 except shutil.Error:
126@@ -168,33 +181,30 @@ def generate_test_config(interface, ssid, psk, address, dhcp):
127
128
129 def write_test_config(config):
130- print_head("Write the test netplan config file to disk")
131+ info_header("Write the test netplan config file to disk")
132 with open(NETPLAN_TEST_CFG, "w", encoding="utf-8") as f:
133 f.write(config)
134- print()
135+ info_line()
136
137
138 def delete_test_config():
139- print_head("Delete the test file")
140+ info_header("Delete the test file")
141 os.remove(NETPLAN_TEST_CFG)
142- print()
143+ info_line()
144
145
146 def netplan_apply_config():
147 cmd = "netplan --debug apply"
148- print_cmd(cmd)
149+ info_cmd(cmd)
150 # Make sure the python env used by netplan is from the base snap
151 env = os.environ
152 env.pop('PYTHONHOME', None)
153 env.pop('PYTHONPATH', None)
154 env.pop('PYTHONUSERBASE', None)
155- retcode = sp.call(cmd, shell=True, env=env)
156- if retcode != 0:
157- print("ERROR: failed netplan apply call")
158- print()
159- return False
160- print()
161- return True
162+ output = sp.check_output(cmd, shell=True, stderr=sp.STDOUT, env=env)
163+ for line in output.decode(sys.stdout.encoding).splitlines():
164+ info_line(line)
165+ info_line()
166
167
168 def _get_networkctl_state(interface):
169@@ -207,45 +217,46 @@ def _get_networkctl_state(interface):
170
171
172 def wait_for_routable(interface, max_wait=30):
173- routable = False
174 attempts = 0
175- while not routable and attempts < max_wait:
176+ while attempts < max_wait:
177 state = _get_networkctl_state(interface)
178- print(state)
179+ info_line(state)
180 if "routable" in state:
181- routable = True
182- break
183+ info_line("Reached routable state")
184+ info_line()
185+ return
186 time.sleep(1)
187 attempts += 1
188- if routable:
189- print("Reached routable state")
190+
191+ if "degraded" in state:
192+ raise RuntimeError("ERROR: degraded state, no IP address assigned")
193 else:
194- if "degraded" in state:
195- print("ERROR: degraded state, no IP address assigned")
196- else:
197- print("ERROR: did not reach routable state")
198- print()
199- return routable
200+ raise RuntimeError("ERROR: did not reach routable state")
201
202
203 def print_address_info(interface):
204 cmd = 'ip address show dev {}'.format(interface)
205- print_cmd(cmd)
206- sp.call(cmd, shell=True)
207- print()
208+ info_cmd(cmd)
209+ output = sp.check_output(cmd, shell=True, stderr=sp.STDOUT)
210+ for line in output.decode(sys.stdout.encoding).splitlines():
211+ info_line(line)
212+ info_line()
213
214
215 def print_route_info():
216 cmd = 'ip route'
217- print_cmd(cmd)
218- sp.call(cmd, shell=True)
219- print()
220+ info_cmd(cmd)
221+ output = sp.check_output(cmd, shell=True, stderr=sp.STDOUT)
222+ for line in output.decode(sys.stdout.encoding).splitlines():
223+ info_line(line)
224+ info_line()
225
226
227 def perform_ping_test(interface):
228 target = None
229 cmd = 'networkctl status --no-pager --no-legend {}'.format(interface)
230- print_cmd(cmd)
231+ info_cmd(cmd)
232+ # get ip to ping
233 output = sp.check_output(cmd, shell=True)
234 for line in output.decode(sys.stdout.encoding).splitlines():
235 vals = line.strip().split(' ')
236@@ -253,29 +264,31 @@ def perform_ping_test(interface):
237 if vals[0] == 'Gateway:':
238 target = vals[1]
239 print('Got gateway address: {}'.format(target))
240-
241+ # do the test
242 if target:
243 count = 5
244 result = ping(target, interface, count, 10, True)
245 if result['received'] == count:
246- return True
247-
248- return False
249+ info_line("Connection test passed")
250+ info_line()
251+ return
252+ raise RuntimeError("Connection test failed")
253
254
255 def print_journal_entries(start):
256- print_head("Journal Entries")
257+ info_header("Journal Entries")
258 cmd = ('journalctl -q --no-pager '
259 '-u systemd-networkd.service '
260 '-u wpa_supplicant.service '
261 ' -u netplan-* '
262 '--since "{}" '.format(start.strftime('%Y-%m-%d %H:%M:%S')))
263- print_cmd(cmd)
264- sp.call(cmd, shell=True)
265+ info_cmd(cmd)
266+ output = sp.check_output(cmd, shell=True)
267+ for line in output.decode(sys.stdout.encoding).splitlines():
268+ info_line(line)
269
270
271 def main():
272- # Read arguments
273 parser = argparse.ArgumentParser(
274 description=("This script will test wireless network with netplan"
275 " in client mode."))
276@@ -305,57 +318,55 @@ def main():
277
278 start_time = datetime.datetime.now()
279
280- netplan_config_backup()
281- netplan_config_wipe()
282-
283 # Create wireless network test configuration file
284- print_head("Generate a test netplan configuration")
285+ info_header("Generate a test netplan configuration")
286 config_data = generate_test_config(**vars(args))
287- print(config_data)
288- print()
289+ info_line(config_data)
290+ info_line()
291
292- write_test_config(config_data)
293+ netplan_config_backup()
294+ netplan_config_wipe()
295
296- # Bring up the interface
297- print_head("Apply the test configuration")
298- if not netplan_apply_config():
299- delete_test_config()
300- netplan_config_restore()
301- print_journal_entries(start_time)
302- raise SystemExit(1)
303- time.sleep(20)
304+ try:
305+ write_test_config(config_data)
306
307- print_head("Wait for interface to be routable")
308- reached_routable = wait_for_routable(args.interface)
309+ # Bring up the interface
310+ info_header("Apply the test configuration")
311+ netplan_apply_config()
312+ time.sleep(20)
313
314- test_result = False
315- if reached_routable:
316- print_head("Display address")
317+ info_header("Wait for interface to be routable")
318+ wait_for_routable(args.interface)
319+
320+ info_header("Display address")
321 print_address_info(args.interface)
322
323- print_head("Display route table")
324+ info_header("Display route table")
325 print_route_info()
326
327 # Check connection by ping or link status
328- print_head("Perform a ping test")
329- test_result = perform_ping_test(args.interface)
330- if test_result:
331- print("Connection test passed\n")
332- else:
333- print("Connection test failed\n")
334-
335- delete_test_config()
336- netplan_config_restore()
337-
338- if not netplan_apply_config():
339+ info_header("Perform a ping test")
340+ perform_ping_test(args.interface)
341+ except sp.CalledProcessError as cpe:
342+ for line in cpe.output.decode(sys.stdout.encoding).splitlines():
343+ logging.critical("%s", line)
344+ info_line()
345 print_journal_entries(start_time)
346- raise SystemExit("ERROR: failed to apply restored config")
347-
348- if not test_result:
349+ info_line()
350+ raise SystemExit("FAILED")
351+ except RuntimeError as re:
352+ logging.critical(str(re))
353 print_journal_entries(start_time)
354- raise SystemExit(1)
355+ info_line()
356+ raise SystemExit("FAILED")
357+ finally:
358+ delete_test_config()
359+ netplan_config_restore()
360+ netplan_apply_config()
361
362 print_journal_entries(start_time)
363+ info_line()
364+ info_line("PASSED")
365
366
367 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches