Merge ~liaou3/checkbox-iiotg/+git/checkbox-provider-intliotg:refactor_tsn_timesync into ~checkbox-dev/checkbox-iiotg/+git/checkbox-provider-intliotg:master
- Git
- lp:~liaou3/checkbox-iiotg/+git/checkbox-provider-intliotg
- refactor_tsn_timesync
- Merge into master
Status: | Merged |
---|---|
Approved by: | Vincent Liao |
Approved revision: | 3546365f56347c6e982df80c7043fcd6055d1ceb |
Merged at revision: | ba071093692d9392352e02dbc9b69519446c2cdc |
Proposed branch: | ~liaou3/checkbox-iiotg/+git/checkbox-provider-intliotg:refactor_tsn_timesync |
Merge into: | ~checkbox-dev/checkbox-iiotg/+git/checkbox-provider-intliotg:master |
Diff against target: |
857 lines (+575/-186) 4 files modified
bin/tsn.py (+550/-0) dev/null (+0/-49) units/tsn/jobs.pxu (+20/-129) units/tsn/test-plan.pxu (+5/-8) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
StanleyHuang | Approve | ||
Rick Wu | Approve | ||
Weichen Wu | Pending | ||
Vic Liu | Pending | ||
PeiYao Chang | Pending | ||
Review via email: mp+464398@code.launchpad.net |
Commit message
Refactor TSN 802.1as-2011
Description of the change
I refactor TSN testing from shell script to python.
submissions:
https:/
https:/
StanleyHuang (stanley31) wrote : | # |
I have some suggestions in inline comments and one questions as following.
- why we need to run clear_qdisc_
Vincent Liao (liaou3) wrote : | # |
@Rick
The reason why I use `/usr/share/
If yes, of course it works.
If not, it is better if we use `/usr/share/
Vincent Liao (liaou3) wrote : | # |
> @Rick
>
> The reason why I use `/usr/share/
> default instead of `"$SNAP"
> slave.cfg` is we are not sure if the user is running this script with
> checkbox.
> If yes, of course it works.
> If not, it is better if we use `/usr/share/
> slave.cfg` since it is the defaule config for linuxptp.
And I also reckon it is not a good idea to use `environment variable` in the python script. We should use arg parse instead of depending on environment variable.
Preview Diff
1 | diff --git a/bin/tsn.py b/bin/tsn.py |
2 | new file mode 100755 |
3 | index 0000000..c85e686 |
4 | --- /dev/null |
5 | +++ b/bin/tsn.py |
6 | @@ -0,0 +1,550 @@ |
7 | +#!/usr/bin/python3 |
8 | + |
9 | +import argparse |
10 | +import subprocess |
11 | +import shlex |
12 | +from threading import Event |
13 | +from typing import List |
14 | +from contextlib import contextmanager |
15 | + |
16 | + |
17 | +def clear_qdisc_settings(interface: str) -> None: |
18 | + """Clear the previous qdisc settings. |
19 | + |
20 | + This function clears the previous qdisc settings by running the tc |
21 | + command with the 'qdisc del' option. It may return an error if there |
22 | + is no previous settings. |
23 | + This will get errors if there is no previous settings. |
24 | + |
25 | + Args: |
26 | + interface (str): The name of the network interface. |
27 | + |
28 | + Returns: |
29 | + None |
30 | + """ |
31 | + # Build the tc command to delete the root qdisc settings |
32 | + cmd = "tc qdisc del dev {} root".format(interface) |
33 | + |
34 | + # Run the tc command with shell=True and a timeout of 1 second |
35 | + subprocess.run(cmd, shell=True, timeout=1) |
36 | + |
37 | + |
38 | +@contextmanager |
39 | +def clear_qdisc_settings_before_and_after(interface: str): |
40 | + """Clear the previous qdisc settings. |
41 | + |
42 | + This context manager clears the previous qdisc settings by running the |
43 | + tc command with the 'qdisc del' option. It may return an error if there |
44 | + is no previous settings. |
45 | + |
46 | + Args: |
47 | + interface (str): The name of the network interface. |
48 | + |
49 | + Yields: |
50 | + None |
51 | + |
52 | + Raises: |
53 | + subprocess.CalledProcessError: If the tc command fails to delete |
54 | + the root qdisc settings. |
55 | + """ |
56 | + |
57 | + # Run the tc command to delete the root qdisc settings |
58 | + try: |
59 | + # Clear qdisc settings before the function call |
60 | + clear_qdisc_settings(interface) |
61 | + yield |
62 | + finally: |
63 | + # Clear qdisc settings after the function call |
64 | + clear_qdisc_settings(interface) |
65 | + |
66 | + |
67 | +def clear_qdisc_settings_before_and_after_decorator(func): |
68 | + """ |
69 | + Decorator that clears the qdisc settings before and after calling |
70 | + the decorated function. |
71 | + |
72 | + Args: |
73 | + func (Callable): The function to be decorated. |
74 | + |
75 | + Returns: |
76 | + Callable: The decorated function. |
77 | + |
78 | + This decorator wraps the given function with a context manager that |
79 | + clears the qdisc settings before and after the function call. |
80 | + It ensures that the qdisc settings are cleared even if an |
81 | + exception occurs during the function execution. |
82 | + |
83 | + Example usage: |
84 | + @clear_qdisc_settings_before_and_after_decorator |
85 | + def my_function(interface, *args, **kwargs): |
86 | + # Function logic here |
87 | + |
88 | + my_function("eth0") # The qdisc settings will be cleared before |
89 | + and after the function call |
90 | + """ |
91 | + |
92 | + def wrapper(interface, *args, **kwargs): |
93 | + """ |
94 | + Wrapper function that clears the qdisc settings before and |
95 | + after calling the decorated function. |
96 | + |
97 | + Args: |
98 | + interface (str): The interface to set the clock on. |
99 | + *args: Variable length argument list. |
100 | + **kwargs: Arbitrary keyword arguments. |
101 | + |
102 | + This function wraps the given function with a context manager that |
103 | + clears the qdisc settings before and after the function call. |
104 | + It ensures that the qdisc settings are cleared even if an exception |
105 | + occurs during the function execution. |
106 | + """ |
107 | + with clear_qdisc_settings_before_and_after(interface): |
108 | + # Call the decorated function with the given arguments |
109 | + # and keyword arguments |
110 | + func(interface, *args, **kwargs) |
111 | + |
112 | + return wrapper |
113 | + |
114 | + |
115 | +def ptp4l(interface: str, cfg: str, timeout: int = 0) -> subprocess.Popen: |
116 | + """Run ptp4l command to sync physical hardware clock between systems. |
117 | + |
118 | + Args: |
119 | + interface (str): The interface to set the clock on. |
120 | + cfg (str): The path to the configuration file. |
121 | + timeout (int): The time to wait for the command to complete, \ |
122 | + in seconds. |
123 | + |
124 | + Returns: |
125 | + subprocess.Popen: A process object representing \ |
126 | + the running ptp4l command. |
127 | + """ |
128 | + # Build the ptp4l command with the provided parameters. |
129 | + cmd = "timeout {} ptp4l -i {} -f {} -m".format( |
130 | + timeout, |
131 | + interface, |
132 | + cfg, |
133 | + ) |
134 | + |
135 | + # Run the ptp4l command with the provided parameters. |
136 | + # The command is run with stdout and stderr redirected to pipes. |
137 | + # Text mode is enabled to allow access to the output as text. |
138 | + process = subprocess.Popen( |
139 | + shlex.split(cmd), |
140 | + stdout=subprocess.PIPE, # Redirect stdout to a pipe. |
141 | + stderr=subprocess.PIPE, # Redirect stderr to a pipe. |
142 | + text=True, # Enable text mode, so output can be accessed as text. |
143 | + ) |
144 | + |
145 | + # Return the process object representing the running ptp4l command. |
146 | + return process |
147 | + |
148 | + |
149 | +def phc2sys(interface: str, timeout: int = 60) -> subprocess.Popen: |
150 | + """Run phc2sys command to sync system clock to physical hardware clock. |
151 | + |
152 | + Args: |
153 | + interface (str): The network interface to sync. |
154 | + timeout (int): The time to wait for the command to complete, |
155 | + in seconds. Defaults to 60 seconds. |
156 | + |
157 | + Returns: |
158 | + subprocess.Popen: A process object representing the |
159 | + running phc2sys command. |
160 | + """ |
161 | + # Build the phc2sys command with the provided parameters. |
162 | + # The command uses the timeout utility to limit the execution time. |
163 | + # phc2sys is used to sync the system clock to the physical hardware clock. |
164 | + # The -s specifies the interface to sync. |
165 | + # The -O 0 flag sets the offset between system clock |
166 | + # and physical hardware clock to 0. |
167 | + # The -c specify the slave clock source. |
168 | + # The -w flag wait for ptp4l. |
169 | + # The -m flag print the messages. |
170 | + # The --step_threshold=1 flag sets the step threshold to 1. |
171 | + # The --transportSpecific=1 the transport specific field. [0-255] |
172 | + cmd = ( |
173 | + "timeout {} phc2sys -s {} " |
174 | + "-O 0 -c CLOCK_REALTIME -w -m " |
175 | + "--step_threshold=1 --transportSpecific=1" |
176 | + ).format(timeout, interface) |
177 | + |
178 | + # Run the phc2sys command with the provided parameters. |
179 | + # The command is run with stdout and stderr redirected to pipes. |
180 | + # Text mode is enabled to allow access to the output as text. |
181 | + process = subprocess.Popen( |
182 | + shlex.split(cmd), |
183 | + stdout=subprocess.PIPE, # Redirect stdout to a pipe. |
184 | + stderr=subprocess.PIPE, # Redirect stderr to a pipe. |
185 | + text=True, # Enable text mode, so output can be accessed as text. |
186 | + ) |
187 | + |
188 | + # Return the process object representing the running phc2sys command. |
189 | + return process |
190 | + |
191 | + |
192 | +def server_mode( |
193 | + interfaces: List, |
194 | + cfg: str = "/usr/share/doc/linuxptp/configs/automotive-master.cfg", |
195 | +) -> None: |
196 | + """Run ptp4l as master in every port. |
197 | + |
198 | + Args: |
199 | + interfaces (List): List of network interfaces. |
200 | + cfg (str, optional): Path to the configuration file. |
201 | + Defaults to |
202 | + "/usr/share/doc/linuxptp/configs/automotive-master.cfg". |
203 | + |
204 | + This function runs ptp4l as master in every port specified |
205 | + in the interfaces list. |
206 | + It terminates all running ptp4l processes on KeyboardInterrupt. |
207 | + """ |
208 | + |
209 | + # List to store the process objects |
210 | + processes = [] |
211 | + |
212 | + # Iterate over each interface and run ptp4l as master |
213 | + for interface in interfaces: |
214 | + # Clear qdisc settings for the interface |
215 | + clear_qdisc_settings(interface=interface) |
216 | + |
217 | + # Run ptp4l as master with the provided interface and configuration |
218 | + process = ptp4l(interface=interface, cfg=cfg) |
219 | + |
220 | + # Add the process object to the list |
221 | + processes.append(process) |
222 | + |
223 | + # Print message about the running ptp4l process |
224 | + print("Start running ptp4l on {} as master".format(interface)) |
225 | + |
226 | + # Print message to press ctrl + c to end this |
227 | + print("Press ctrl + c to end this.") |
228 | + |
229 | + try: |
230 | + # Wait for KeyboardInterrupt |
231 | + Event().wait() |
232 | + except KeyboardInterrupt: |
233 | + # Terminate all running ptp4l processes |
234 | + for process in processes: |
235 | + process.terminate() |
236 | + |
237 | + # Print message about terminating all ptp4l processes |
238 | + print("Terminated all ptp4l process") |
239 | + |
240 | + # Return None |
241 | + return |
242 | + |
243 | + |
244 | +@clear_qdisc_settings_before_and_after_decorator |
245 | +def time_sync_ptp4l( |
246 | + interface: str, |
247 | + cfg: str = "/usr/share/doc/linuxptp/configs/automotive-slave.cfg", |
248 | + timeout: int = 60, |
249 | +) -> None: |
250 | + """ |
251 | + Test ptp4l by running it as a subprocess and checking its output. |
252 | + |
253 | + Args: |
254 | + interface (str): The network interface to run ptp4l on. |
255 | + cfg (str, optional): The path to the ptp4l configuration file. |
256 | + Defaults to "/usr/share/doc/linuxptp/configs/automotive-slave.cfg". |
257 | + timeout (int, optional): The maximum time to wait for ptp4l to run. |
258 | + Defaults to 60 seconds. |
259 | + |
260 | + Raises: |
261 | + SystemExit: If ptp4l encounters an error or the master offset is not |
262 | + between -100 and 100. |
263 | + |
264 | + Prints: |
265 | + Standard Output (stdout): The output of ptp4l. |
266 | + Standard Error (stderr): The error output of ptp4l, if any. |
267 | + [PASS] Masteroffset is between -100 to 100: If the master offset is |
268 | + between -100 and 100. |
269 | + [FAIL] Masteroffset is not between -100 to 100: If the master offset |
270 | + is not between -100 and 100. |
271 | + """ |
272 | + |
273 | + # Run ptp4l as a subprocess and get its output |
274 | + process = ptp4l(interface=interface, cfg=cfg, timeout=timeout) |
275 | + stdout, stderr = process.communicate() |
276 | + |
277 | + # Print the output of ptp4l |
278 | + print("Standard Output (stdout):") |
279 | + print(stdout) |
280 | + print("Standard Error (stderr):") |
281 | + print(stderr) |
282 | + |
283 | + # If ptp4l encountered an error, raise a SystemExit exception |
284 | + if stderr: |
285 | + raise SystemExit( |
286 | + "[Error] Catch error while running ptp4l on {}".format(interface) |
287 | + ) |
288 | + |
289 | + # Check the last 10 seconds of ptp4l output |
290 | + lines = stdout.splitlines() |
291 | + for line in lines[-10:]: |
292 | + offset = int(line.split()[3]) |
293 | + if not -100 < offset < 100: |
294 | + raise SystemExit("[FAIL] Masteroffset is not between -100 to 100") |
295 | + |
296 | + # If the master offset is between -100 and 100, print a success message |
297 | + print("[PASS] Masteroffset is between -100 to 100") |
298 | + |
299 | + |
300 | +@clear_qdisc_settings_before_and_after_decorator |
301 | +def time_sync_phc2sys( |
302 | + interface: str, |
303 | + cfg: str = "/usr/share/doc/linuxptp/configs/automotive-slave.cfg", |
304 | + timeout: int = 60, |
305 | +) -> None: |
306 | + """ |
307 | + Test phc2sys by running it as a subprocess and checking its output. |
308 | + |
309 | + Args: |
310 | + interface (str): The network interface to run phc2sys on. |
311 | + cfg (str, optional): The path to the phc2sys configuration file. |
312 | + Defaults to "/usr/share/doc/linuxptp/configs/automotive-slave.cfg". |
313 | + timeout (int, optional): The maximum time to wait for phc2sys to run. |
314 | + Defaults to 60 seconds. |
315 | + |
316 | + Raises: |
317 | + SystemExit: If phc2sys encounters an error or the master offset is not |
318 | + between -100 and 100, or the state is not equal to "s2" for the |
319 | + last 10 seconds, or the path delay is not equal to 0. |
320 | + |
321 | + Prints: |
322 | + Standard Output (stdout): The output of phc2sys. |
323 | + Standard Error (stderr): The error output of phc2sys, if any. |
324 | + [PASS] Syncing system time to physical hardware clock successfully: If |
325 | + phc2sys syncs the system time to physical hardware clock |
326 | + successfully. |
327 | + """ |
328 | + |
329 | + # Run ptp4l as a subprocess and get its output |
330 | + ptp4l(interface=interface, cfg=cfg, timeout=timeout) |
331 | + |
332 | + # Run phc2sys as a subprocess and get its output |
333 | + process = phc2sys(interface=interface, timeout=timeout) |
334 | + stdout, stderr = process.communicate() |
335 | + |
336 | + # Print the output of phc2sys |
337 | + print("Standard Output (stdout):") |
338 | + print(stdout) |
339 | + print("Standard Error (stderr):") |
340 | + print(stderr) |
341 | + |
342 | + # If phc2sys encountered an error, raise a SystemExit exception |
343 | + if stderr: |
344 | + print(f"[Error] Catch error while running ptp4l on {interface}") |
345 | + raise SystemExit( |
346 | + "[Error] Catch error while running ptp4l on {}".format(interface) |
347 | + ) |
348 | + |
349 | + # Check the last 10 seconds of phc2sys output |
350 | + lines = stdout.splitlines() |
351 | + for line in lines[-10:]: |
352 | + offset = int(line.split()[4]) |
353 | + state = line.split()[5] |
354 | + delay = int(line.split()[9]) |
355 | + |
356 | + # If the master offset is not between -100 and 100, |
357 | + # raise a SystemExit exception |
358 | + if not -100 < offset < 100: |
359 | + print("[FAIL] phc offset is not between -100 to 100") |
360 | + raise SystemExit(1) |
361 | + |
362 | + # If the state is not equal to "s2" for the last 10 seconds, |
363 | + # raise a SystemExit exception |
364 | + if state != "s2": |
365 | + raise SystemExit("[FAIL] state is not equal to s2 " |
366 | + "for the last 10 seconds\n" |
367 | + "s0: unsynced\n" |
368 | + "s1: syncing\n" |
369 | + "s2: synced") |
370 | + |
371 | + # If the path delay is not equal to 0 for the last 10 seconds, |
372 | + # raise a SystemExit exception |
373 | + if delay != 0: |
374 | + raise SystemExit("[FAIL] path delay is not equal to 0\n" |
375 | + "path delay should be 0 if using hardware " |
376 | + "cross timestamping") |
377 | + |
378 | + # If phc2sys syncs the system time to physical hardware clock successfully, |
379 | + # print a success message |
380 | + print("[PASS] Syncing system time to physical hardware clock successfully") |
381 | + |
382 | + |
383 | +@clear_qdisc_settings_before_and_after_decorator |
384 | +def time_based_shaper(interface: str, timeout: int = 10) -> None: |
385 | + """ |
386 | + Setup a time-based shaper on the specified interface. |
387 | + |
388 | + Args: |
389 | + interface (str): The interface to set the shaper on. |
390 | + timeout (int): The timeout for the shaper in seconds. |
391 | + |
392 | + Raises: |
393 | + SystemExit: If there are more than 5% packets not within the required |
394 | + time interval. |
395 | + """ |
396 | + |
397 | + # Add mqprio qdisc with four traffic classes |
398 | + cmd = ( |
399 | + "tc qdisc add dev {} handle 8001: parent root mqprio num_tc 4 " |
400 | + "map 0 1 2 3 3 3 3 3 3 3 3 3 3 3 3 3 queues 1@0 1@1 1@2 1@3 hw 0" |
401 | + ).format(interface) |
402 | + subprocess.run(cmd, shell=True, timeout=1) |
403 | + |
404 | + # Replace parent qdisc with etf offload |
405 | + cmd = ( |
406 | + "tc qdisc replace dev {} parent 8001:4 etf offload clockid CLOCK_TAI " |
407 | + "delta 500000" |
408 | + ).format(interface) |
409 | + subprocess.run(cmd, shell=True, timeout=1) |
410 | + |
411 | + # Show the current qdisc settings |
412 | + cmd = "tc qdisc show dev {}".format(interface) |
413 | + subprocess.run(cmd, shell=True, timeout=1) |
414 | + |
415 | + # Run udp_tai with specified parameters |
416 | + cmd = "udp_tai -c 3 -i {} -P 1000000 -p 90 -d 600000".format(interface) |
417 | + process_udp_tai = subprocess.Popen( |
418 | + shlex.split(cmd), stdout=subprocess.PIPE, text=True |
419 | + ) |
420 | + |
421 | + # Capture packets with tcpdump and |
422 | + # check that they are within the required time interval |
423 | + cmd = ( |
424 | + "tcpdump -G {} -Q out -ttt -ni {} --time-stamp-precision=nano " |
425 | + "-j adapter_unsynced port 7788 -c {}".format( |
426 | + timeout, interface, timeout * 1000 |
427 | + ) |
428 | + ) |
429 | + process = subprocess.Popen( |
430 | + shlex.split(cmd), stdout=subprocess.PIPE, text=True |
431 | + ) |
432 | + try: |
433 | + stdout, stderr = process.communicate(timeout=timeout * 2) |
434 | + except subprocess.TimeoutExpired: |
435 | + process.kill() |
436 | + raise SystemExit("Reach timeout {}".format(timeout * 2)) |
437 | + finally: |
438 | + process_udp_tai.kill() |
439 | + |
440 | + if stdout is None or stderr is not None: |
441 | + raise SystemExit("No output from tcpdump!") |
442 | + |
443 | + # Print the output of tcpdump |
444 | + print("Standard Output (stdout):") |
445 | + print(stdout) |
446 | + print("Standard Error (stderr):") |
447 | + print(stderr) |
448 | + |
449 | + lines = stdout.splitlines() |
450 | + cnt = 0 |
451 | + for line in lines: |
452 | + try: |
453 | + time = int(line.split()[0].split(".")[1]) |
454 | + except (IndexError, ValueError): |
455 | + raise SystemExit( |
456 | + "[ERROR] Cannot find the time in the line: {}".format(line) |
457 | + ) |
458 | + if not 999500 < time < 1000500: |
459 | + cnt += 1 |
460 | + |
461 | + # If there are more than 5% packets not within the required time interval, |
462 | + # raise a SystemExit exception |
463 | + if cnt > timeout * 1000 * 0.05: |
464 | + raise SystemExit( |
465 | + "[FAIL] There are {}/{} (more than 5%) packets not " |
466 | + "within the required time interval (999500 - 1000500)".format( |
467 | + cnt, timeout * 1000 |
468 | + ) |
469 | + ) |
470 | + |
471 | + print( |
472 | + "[PASS] There are {}/{} packets (less than 5%) within " |
473 | + "the required time interval (999500 - 1000500)".format( |
474 | + cnt, timeout * 1000 |
475 | + ) |
476 | + ) |
477 | + |
478 | + |
479 | +def main(): |
480 | + """ |
481 | + Main function to parse command line arguments and perform the |
482 | + specified testing item or server_mode. |
483 | + """ |
484 | + # Create ArgumentParser object |
485 | + parser = argparse.ArgumentParser( |
486 | + prog="TSN Testing Tool", |
487 | + description="This is a tool to help you perform the TSN testing", |
488 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
489 | + ) |
490 | + |
491 | + # Add arguments |
492 | + parser.add_argument( |
493 | + "--run", |
494 | + action="store", |
495 | + choices=["server", "ptp4l", "phc2sys", "time_based_shaper"], |
496 | + help="Run a testing item or server_mode", |
497 | + ) |
498 | + parser.add_argument( |
499 | + "--interfaces", "-i", nargs="+", help="TSN ethernet interface" |
500 | + ) |
501 | + parser.add_argument( |
502 | + "--timeout", |
503 | + "-t", |
504 | + action="store", |
505 | + type=int, |
506 | + default=60, |
507 | + help="Timeout for the testing item", |
508 | + ) |
509 | + parser.add_argument( |
510 | + "--master-config", |
511 | + action="store", |
512 | + type=str, |
513 | + default="/usr/share/doc/linuxptp/configs/automotive-master.cfg", |
514 | + help="gPTP config file for master", |
515 | + ) |
516 | + parser.add_argument( |
517 | + "--client-config", |
518 | + action="store", |
519 | + type=str, |
520 | + default="/usr/share/doc/linuxptp/configs/automotive-slave.cfg", |
521 | + help="gPTP config file for client", |
522 | + ) |
523 | + |
524 | + # Parse command line arguments |
525 | + args = parser.parse_args() |
526 | + |
527 | + # Perform the specified testing item or server_mode |
528 | + if args.run == "server": |
529 | + # Run server_mode |
530 | + server_mode(args.interfaces, cfg=args.master_config) |
531 | + return |
532 | + elif len(args.interfaces) != 1: |
533 | + # Exit if interfaces is not a single element |
534 | + raise SystemExit("We only need one interface for testing!") |
535 | + |
536 | + if args.run == "ptp4l": |
537 | + # Time sync with ptp4l |
538 | + time_sync_ptp4l( |
539 | + args.interfaces[0], |
540 | + cfg=args.client_config, |
541 | + timeout=args.timeout, |
542 | + ) |
543 | + elif args.run == "phc2sys": |
544 | + # Time sync with phc2sys |
545 | + time_sync_phc2sys( |
546 | + args.interfaces[0], |
547 | + cfg=args.client_config, |
548 | + timeout=args.timeout, |
549 | + ) |
550 | + elif args.run == "time_based_shaper": |
551 | + # Time based shaper |
552 | + time_based_shaper(interface=args.interfaces[0], timeout=args.timeout) |
553 | + |
554 | + |
555 | +if __name__ == "__main__": |
556 | + main() |
557 | diff --git a/bin/tsn_time_based_shaper.sh b/bin/tsn_time_based_shaper.sh |
558 | deleted file mode 100755 |
559 | index 6106700..0000000 |
560 | --- a/bin/tsn_time_based_shaper.sh |
561 | +++ /dev/null |
562 | @@ -1,49 +0,0 @@ |
563 | -#!/bin/bash |
564 | - |
565 | -set -e |
566 | - |
567 | -timeout=$1 |
568 | -eth_interface=$2 |
569 | -server_user=$3 |
570 | -server_ip=$4 |
571 | -server_pwd=$5 |
572 | -server_eth_interface=$6 |
573 | - |
574 | -echo "## Clearing previous queue discipline configuration..." |
575 | -echo "## You will get errors if there's no previous configuration, but you could ignore it." |
576 | -tc qdisc del dev "$eth_interface" root || true |
577 | -echo "" |
578 | - |
579 | -echo "## Setting time schedule..." |
580 | -tc qdisc add dev "$eth_interface" handle 8001: parent root mqprio num_tc 4 map 0 1 2 3 3 3 3 3 3 3 3 3 3 3 3 3 queues 1@0 1@1 1@2 1@3 hw 0 |
581 | -tc qdisc replace dev "$eth_interface" parent 8001:4 etf offload clockid CLOCK_TAI delta 500000 |
582 | -echo "" |
583 | - |
584 | -echo "## Showing qdisc settings..." |
585 | -tc qdisc show dev "$eth_interface" |
586 | -echo "" |
587 | - |
588 | -echo "## Start sending and receiving udp packets..." |
589 | -out=$(mktemp) |
590 | -timeout "$timeout" udp_tai -c 3 -i "$eth_interface" -P 1000000 -p 90 -d 600000 > /dev/null & |
591 | -(sshpass -p "u" ssh -o StrictHostKeyChecking=no "$server_user"@"$server_ip" "echo $server_pwd | sudo -S tcpdump -G $timeout -Q in -ttt -ni $server_eth_interface --time-stamp-precision=nano -j adapter_unsynced port 7788 -c 20000") | tee "$out" |
592 | - |
593 | -echo "## Checking result..." |
594 | -lines=$(tail -n 10000 "$out") |
595 | -count=0 |
596 | -while read -r line |
597 | -do |
598 | - interval=$(echo "$line" | awk '{print $1}' | awk -F "." '{print $2}') |
599 | - if [ "$interval" -gt 1000500 ] || [ "$interval" -lt 999500 ]; then |
600 | - count=$((count+1)) |
601 | - fi |
602 | -done <<< "$lines" |
603 | - |
604 | -echo "In the last 10000 packets there are $count packets are not within 1000000+/-500 ns interval." |
605 | -echo "Pass condition: Less than 5% packets not within the required interval" |
606 | -if [ $count -lt 500 ]; then |
607 | - echo "PASS" |
608 | -else |
609 | - echo "FAIL" |
610 | - exit 1 |
611 | -fi |
612 | diff --git a/units/tsn/jobs.pxu b/units/tsn/jobs.pxu |
613 | index 92f6082..cd98d21 100644 |
614 | --- a/units/tsn/jobs.pxu |
615 | +++ b/units/tsn/jobs.pxu |
616 | @@ -27,31 +27,6 @@ command: |
617 | exit "$exit_code" |
618 | |
619 | unit: job |
620 | -id: tsn/testing-snap-install-check |
621 | -category_id: tsn |
622 | -_summary: TSN testing snap installation check |
623 | -_description: |
624 | - To check if the TSN related snap is installed |
625 | - - linuxptp-rt |
626 | - - ethtool-rt |
627 | - - iproute2-rt |
628 | -depends: tsn/tsn-detected |
629 | -plugin: shell |
630 | -command: |
631 | - snap_list="linuxptp-rt ethtool-rt iproute2-rt" |
632 | - exit_code=0 |
633 | - for snap in $snap_list; do |
634 | - if ! snap list | grep -q $snap; then |
635 | - echo "ERROR: snap package \"${snap}\" not found!" |
636 | - echo "Try install it with the command below" |
637 | - echo "sudo snap install ${snap} --edge --devmode" |
638 | - echo "" |
639 | - exit_code=1 |
640 | - fi |
641 | - done |
642 | - exit $exit_code |
643 | - |
644 | -unit: job |
645 | category_id: tsn |
646 | id: tsn/tsn-devices |
647 | plugin: resource |
648 | @@ -145,146 +120,62 @@ unit: template |
649 | template-resource: tsn/tsn-devices |
650 | template-unit: job |
651 | template-engine: jinja2 |
652 | -id: tsn/ieee802.1as-2011/time-sync-between-two-system-as-client-{{ interface }} |
653 | -category_id: tsn |
654 | -_summary: |
655 | - Check establish time-sync with another machine as client via {{ interface }} |
656 | -_purpose: |
657 | - Use {{ interface }} on the SUT to achieve time sync with the second machine as client |
658 | -depends: |
659 | - tsn/tsn-detected |
660 | - tsn/testing-snap-install-check |
661 | -plugin: shell |
662 | -user: root |
663 | -environ: |
664 | - TSN_SERVER_IP |
665 | - TSN_SERVER_USER |
666 | - TSN_SERVER_PWD |
667 | - TSN_SERVER_INTERFACE |
668 | - TSN_MAX_MASTER_OFFSET |
669 | - TSN_SYNC_TIME |
670 | -command: |
671 | - set -e |
672 | - out=$(mktemp) |
673 | - tsn_time_sync.sh ${TSN_SYNC_TIME:-60} client {{ interface }} "$TSN_SERVER_USER" "$TSN_SERVER_IP" "$TSN_SERVER_PWD" "$TSN_SERVER_INTERFACE" | tee "$out" |
674 | - echo "" |
675 | - lines=$(tail -n 10 "$out") |
676 | - while read -r line |
677 | - do |
678 | - offset=$(echo "$line" | grep -o 'master offset\s*-\?[0-9]\+ s' | awk '{print $3}') |
679 | - if [ "$offset" -gt "${TSN_MAX_MASTER_OFFSET:-100}" ] || [ "$offset" -lt -"${TSN_MAX_MASTER_OFFSET:-100}" ]; then |
680 | - echo "FAIL: master offset is not within -"${TSN_MAX_MASTER_OFFSET:-100}" to "${TSN_MAX_MASTER_OFFSET:-100}" for the last 10 seconds." |
681 | - exit 1 |
682 | - fi |
683 | - done <<< "$lines" |
684 | - echo "PASS: master offset is within -"${TSN_MAX_MASTER_OFFSET:-100}" to "${TSN_MAX_MASTER_OFFSET:-100}" for the last 10 seconds." |
685 | - |
686 | -unit: template |
687 | -template-resource: tsn/tsn-devices |
688 | -template-unit: job |
689 | -template-engine: jinja2 |
690 | -id: tsn/ieee802.1as-2011/time-sync-between-two-system-as-master-{{ interface }} |
691 | -category_id: tsn |
692 | -_summary: |
693 | - Check establish time-sync with another machine as master via {{ interface }} |
694 | -_purpose: |
695 | - Use {{ interface }} on the SUT to achieve time sync with the second machine as master |
696 | -depends: |
697 | - tsn/tsn-detected |
698 | -plugin: shell |
699 | -user: root |
700 | -environ: |
701 | - TSN_SERVER_IP |
702 | - TSN_SERVER_USER |
703 | - TSN_SERVER_PWD |
704 | - TSN_SERVER_INTERFACE |
705 | - TSN_MAX_MASTER_OFFSET |
706 | - TSN_SYNC_TIME |
707 | -command: |
708 | - set -e |
709 | - out=$(mktemp) |
710 | - tsn_time_sync.sh ${TSN_SYNC_TIME:-60} master {{ interface }} "$TSN_SERVER_USER" "$TSN_SERVER_IP" "$TSN_SERVER_PWD" "$TSN_SERVER_INTERFACE" | tee "$out" |
711 | - echo "" |
712 | - lines=$(tail -n 10 "$out") |
713 | - while read -r line |
714 | - do |
715 | - offset=$(echo "$line" | grep -o 'master offset\s*-\?[0-9]\+ s' | awk '{print $3}') |
716 | - if [ "$offset" -gt "${TSN_MAX_MASTER_OFFSET:-100}" ] || [ "$offset" -lt -"${TSN_MAX_MASTER_OFFSET:-100}" ]; then |
717 | - echo "FAIL: master offset is not within -"${TSN_MAX_MASTER_OFFSET:-100}" to "${TSN_MAX_MASTER_OFFSET:-100}" for the last 10 seconds." |
718 | - exit 1 |
719 | - fi |
720 | - done <<< "$lines" |
721 | - echo "PASS: master offset is within -"${TSN_MAX_MASTER_OFFSET:-100}" to "${TSN_MAX_MASTER_OFFSET:-100}" for the last 10 seconds." |
722 | - |
723 | -unit: template |
724 | -template-resource: tsn/tsn-devices |
725 | -template-unit: job |
726 | -template-engine: jinja2 |
727 | -id: tsn/ieee802.1as-2011/time-sync-system-clock-with-PHC-as-client-{{ interface }} |
728 | +template-id: tsn/ieee802-1as-2011/ptp4l-interfaces |
729 | +_template-summary: Time sync to another machine via ethernet interfaces |
730 | +id: tsn/ieee802-1as-2011/ptp4l-{{ interface }} |
731 | category_id: tsn |
732 | _summary: |
733 | - System time sync with PTP hardware clock as client |
734 | + Time sync to another machine as client via {{ interface }} |
735 | depends: |
736 | tsn/tsn-detected |
737 | - tsn/ieee802.1as-2011/time-sync-between-two-system-as-client-{{ interface }} |
738 | plugin: shell |
739 | user: root |
740 | -_purpose: |
741 | - To check if the system can sync time between system clock and PTP hardware clock as client via {{ interface }} |
742 | environ: |
743 | - TSN_SERVER_IP |
744 | - TSN_SERVER_USER |
745 | - TSN_SERVER_PWD |
746 | - TSN_SERVER_INTERFACE |
747 | - TSN_SYNC_TIME |
748 | + SNAP |
749 | + PTP4L_TESTING_TIME |
750 | + PTP_CLIENT_CFG |
751 | command: |
752 | - tsn_phc2sys.sh ${TSN_SYNC_TIME:-60} client {{ interface }} "$TSN_SERVER_USER" "$TSN_SERVER_IP" "$TSN_SERVER_PWD" "$TSN_SERVER_INTERFACE" |
753 | + tsn.py --run ptp4l -i {{ interface }} -t ${PTP4L_TESTING_TIME:-60} --client-config ${PTP_CLIENT_CFG:-"$SNAP"/usr/share/doc/linuxptp/configs/automotive-slave.cfg} |
754 | |
755 | unit: template |
756 | template-resource: tsn/tsn-devices |
757 | template-unit: job |
758 | template-engine: jinja2 |
759 | -id: tsn/ieee802.1as-2011/time-sync-system-clock-with-PHC-as-master-{{ interface }} |
760 | +template-id: tsn/ieee802-1as-2011/phc2sys-interfaces |
761 | +id: tsn/ieee802-1as-2011/phc2sys-{{ interface }} |
762 | category_id: tsn |
763 | _summary: |
764 | - System time sync with PTP hardware clock as master |
765 | + System time sync to PTP hardware clock on {{ interface }} |
766 | depends: |
767 | tsn/tsn-detected |
768 | - tsn/ieee802.1as-2011/time-sync-between-two-system-as-master-{{ interface }} |
769 | + tsn/ieee802-1as-2011/ptp4l-{{ interface }} |
770 | plugin: shell |
771 | user: root |
772 | -_purpose: |
773 | - To check if the system can sync time between system clock and PTP hardware clock as master via {{ interface }} |
774 | environ: |
775 | - TSN_SERVER_IP |
776 | - TSN_SERVER_USER |
777 | - TSN_SERVER_PWD |
778 | - TSN_SERVER_INTERFACE |
779 | - TSN_SYNC_TIME |
780 | + SNAP |
781 | + PHC2SYS_TESTING_TIME |
782 | + PTP_CLIENT_CFG |
783 | command: |
784 | - tsn_phc2sys.sh ${TSN_SYNC_TIME:-60} master {{ interface }} "$TSN_SERVER_USER" "$TSN_SERVER_IP" "$TSN_SERVER_PWD" "$TSN_SERVER_INTERFACE" |
785 | + tsn.py --run phc2sys -i {{ interface }} -t ${PHC2SYS_TESTING_TIME:-60} --client-config ${PTP_CLIENT_CFG:-"$SNAP"/usr/share/doc/linuxptp/configs/automotive-slave.cfg} |
786 | |
787 | unit: template |
788 | template-resource: tsn/tsn-devices |
789 | template-unit: job |
790 | template-engine: jinja2 |
791 | +template-id: tsn/time-based-shaping/udp-packet-time-check-interfaces |
792 | id: tsn/time-based-shaping/udp-packet-time-check-{{ interface }} |
793 | category_id: tsn |
794 | _summary: UDP packet time intervals check |
795 | depends: |
796 | tsn/tsn-detected |
797 | - tsn/testing-snap-install-check |
798 | plugin: shell |
799 | user: root |
800 | _purpose: |
801 | - To check if the system can transmit udp packet precisely at 1 ms intervals |
802 | + To check if the system can send udp packet precisely at 1 ms intervals |
803 | environ: |
804 | - TSN_SERVER_IP |
805 | - TSN_SERVER_USER |
806 | - TSN_SERVER_PWD |
807 | - TSN_SERVER_INTERFACE |
808 | + TIME_BASED_SHAPER_TESTING_TIME |
809 | command: |
810 | - tsn_time_based_shaper.sh 30 {{ interface }} "$TSN_SERVER_USER" "$TSN_SERVER_IP" "$TSN_SERVER_PWD" "$TSN_SERVER_INTERFACE" |
811 | + tsn.py --run time_based_shaper --interface {{ interface }} --timeout ${TIME_BASED_SHAPER_TESTING_TIME:-10} |
812 | |
813 | unit: template |
814 | template-resource: tsn/tsn-devices |
815 | diff --git a/units/tsn/test-plan.pxu b/units/tsn/test-plan.pxu |
816 | index c5cc31d..97e132c 100644 |
817 | --- a/units/tsn/test-plan.pxu |
818 | +++ b/units/tsn/test-plan.pxu |
819 | @@ -29,13 +29,13 @@ include: |
820 | tsn/test-env-setup-for-.* |
821 | tsn/testing-snap-install-check |
822 | nested_part: |
823 | - tsn-ieee802.1as-2011 |
824 | + tsn/ieee802-1as-2011 |
825 | tsn-ieee802.1qav |
826 | tsn-ieee802.1qbu |
827 | tsn-time-based-shaping |
828 | tsn-ieee802.1qbv |
829 | |
830 | -id: tsn-ieee802.1as-2011 |
831 | +id: tsn/ieee802-1as-2011 |
832 | _name: Time Sensitive Networking Test Plan for IEEE 802.1AS-2011 |
833 | _description: |
834 | This testplan is to check if it could follow IEEE 802.1AS-2011. |
835 | @@ -46,11 +46,8 @@ unit: test plan |
836 | bootstrap_include: |
837 | tsn/tsn-devices |
838 | include: |
839 | - tsn/testing-snap-install-check |
840 | - tsn/ieee802.1as-2011/time-sync-between-two-system-as-client-.* |
841 | - tsn/ieee802.1as-2011/time-sync-between-two-system-as-master-.* |
842 | - tsn/ieee802.1as-2011/time-sync-system-clock-with-PHC-as-client-.* |
843 | - tsn/ieee802.1as-2011/time-sync-system-clock-with-PHC-as-master-.* |
844 | + tsn/ieee802-1as-2011/ptp4l-interfaces |
845 | + tsn/ieee802-1as-2011/phc2sys-interfaces |
846 | |
847 | id: tsn-time-based-shaping |
848 | _name: Time Sensitive Networking Test Plan for Time-Based Shaping |
849 | @@ -63,7 +60,7 @@ bootstrap_include: |
850 | tsn/tsn-devices |
851 | include: |
852 | tsn/testing-snap-install-check |
853 | - tsn/time-based-shaping/udp-packet-time-check-.* |
854 | + tsn/time-based-shaping/udp-packet-time-check-interfaces |
855 | |
856 | id: tsn-ieee802.1qav |
857 | _name: Time Sensitive Networking Test Plan for IEEE 802.1Qav |
Hi Vincent, Please see my inline comment.