27
27
from collections import OrderedDict
28
28
from glob import glob
29
29
from pathlib import Path
30
+ from typing import Dict , List , Optional , Tuple
30
31
31
32
try :
32
33
import scapy .utils as scapy_util
35
36
pass
36
37
37
38
FILE_DIR = Path (__file__ ).resolve ().parent
38
- # Append tools to the import path.
39
- sys .path .append (str (FILE_DIR .joinpath ("../../tools " )))
40
- import testutils
41
- from stf .stf_parser import STFLexer , STFParser
39
+ # Append the root to the import path.
40
+ sys .path .append (str (FILE_DIR .joinpath ("../.." )))
41
+ import tools . testutils as testutils
42
+ from tools . stf .stf_parser import STFLexer , STFParser
42
43
43
44
44
45
class TimeoutException (Exception ):
@@ -52,13 +53,28 @@ def signal_handler(signum, frame):
52
53
signal .signal (signal .SIGALRM , signal_handler )
53
54
54
55
55
- class Options (object ):
56
- def __init__ (self ):
57
- self .binary = None
58
- self .verbose = False
59
- self .preserveTmp = False
60
- self .observationLog = None
61
- self .usePsa = False
56
+ class Options :
57
+ # File that is being compiled.
58
+ p4_file : Path = Path ("." )
59
+ # Path to stf test file that is used.
60
+ test_file : Optional [Path ] = None
61
+ # Actual location of the test framework.
62
+ testdir : Path = Path ("." )
63
+ # The base directory of the compiler..
64
+ rootdir : Path = Path ("." )
65
+ cleanupTmp = True # if false do not remote tmp folder created
66
+ compiler_build_dir : Path = Path ("." ) # path to compiler build directory
67
+ testName : str = "" # Name of the test
68
+ replace : bool = False # replace previous outputs
69
+ compiler_options : List [str ] = []
70
+ switch_options : List [str ] = []
71
+ switch_target_options : List [str ] = []
72
+ has_bmv2 : bool = False # Is the behavioral model installed?
73
+ use_psa : bool = False # Use the psa switch behavioral model?
74
+ run_debugger : str = ""
75
+ # Log packets produced by the BMV2 model if path to log is supplied
76
+ observation_log : Optional [Path ] = None
77
+ init_commands : List [str ] = []
62
78
63
79
64
80
def ByteToHex (byteStr ):
@@ -488,8 +504,8 @@ def p_meter_rate_one(self, p):
488
504
# able to invoke the BMV2 simulator, create a CLI file
489
505
# and test packets in pcap files.
490
506
class RunBMV2 (object ):
491
- def __init__ (self , folder , options , jsonfile ) :
492
- self .clifile = folder + "/ cli.txt"
507
+ def __init__ (self , folder : Path , options : Options , jsonfile : Path ) -> None :
508
+ self .clifile = folder . joinpath ( " cli.txt")
493
509
self .jsonfile = jsonfile
494
510
self .stffile = None
495
511
self .folder = folder
@@ -503,10 +519,8 @@ def __init__(self, folder, options, jsonfile):
503
519
self .actions = []
504
520
self .switchLogFile = "switch.log" # .txt is added by BMv2
505
521
self .readJson ()
506
- self .cmd_line_args = getattr (options , "switchOptions" , ())
507
- self .target_specific_cmd_line_args = getattr (options , "switchTargetSpecificOptions" , ())
508
522
509
- def readJson (self ):
523
+ def readJson (self ) -> None :
510
524
with open (self .jsonfile ) as jf :
511
525
self .json = json .load (jf )
512
526
for a in self .json ["actions" ]:
@@ -516,10 +530,10 @@ def readJson(self):
516
530
for t in self .json ["pipelines" ][1 ]["tables" ]:
517
531
self .tables .append (BMV2Table (t ))
518
532
519
- def filename (self , interface , direction ) :
520
- return self .folder + "/" + self .pcapPrefix + str ( interface ) + "_" + direction + " .pcap"
533
+ def filename (self , interface : str , direction : str ) -> Path :
534
+ return self .folder . joinpath ( f" { self .pcapPrefix } { interface } _ { direction } .pcap")
521
535
522
- def interface_of_filename (self , f ):
536
+ def interface_of_filename (self , f ) -> int :
523
537
return int (os .path .basename (f ).rstrip (".pcap" ).lstrip (self .pcapPrefix ).rsplit ("_" , 1 )[0 ])
524
538
525
539
def do_cli_command (self , cmd ):
@@ -529,7 +543,7 @@ def do_cli_command(self, cmd):
529
543
self .packetDelay = 1
530
544
531
545
def execute_stf_command (self , stf_entry ):
532
- if self . options . verbose and stf_entry :
546
+ if stf_entry :
533
547
testutils .log .info ("STF Command: %s" , stf_entry )
534
548
cmd = stf_entry [0 ]
535
549
if cmd == "" :
@@ -679,14 +693,14 @@ def tableByName(self, tableName):
679
693
return candidate
680
694
raise Exception ("Could not find table " + tableName )
681
695
682
- def interfaceArgs (self ):
696
+ def interfaceArgs (self ) -> List [ str ] :
683
697
# return list of interface names suitable for bmv2
684
698
result = []
685
699
for interface in sorted (self .interfaces ):
686
700
result .append ("-i " + str (interface ) + "@" + self .pcapPrefix + str (interface ))
687
701
return result
688
702
689
- def generate_model_inputs (self , stf_map ) :
703
+ def generate_model_inputs (self , stf_map : Dict [ str , str ]) -> int :
690
704
for entry in stf_map :
691
705
cmd = entry [0 ]
692
706
if cmd in ["packet" , "expect" ]:
@@ -711,11 +725,11 @@ def check_switch_server_ready(self, proc, thriftPort):
711
725
if result == 0 :
712
726
return True
713
727
714
- def run (self , stf_map ) :
728
+ def run (self , stf_map : Dict [ str , str ]) -> int :
715
729
testutils .log .info ("Running model" )
716
730
wait = 0 # Time to wait before model starts running
717
731
718
- if self .options .usePsa :
732
+ if self .options .use_psa :
719
733
switch = "psa_switch"
720
734
switch_cli = "psa_switch_CLI"
721
735
else :
@@ -750,17 +764,15 @@ def run(self, stf_map):
750
764
+ self .interfaceArgs ()
751
765
+ [self .jsonfile ]
752
766
)
753
- if self .cmd_line_args :
754
- runswitch += self .cmd_line_args
755
- if self .target_specific_cmd_line_args :
767
+ runswitch += self .options .switch_options
768
+ if self .options .switch_target_options :
756
769
runswitch += [
757
770
"--" ,
758
- ] + self .target_specific_cmd_line_args
759
- testutils .log .info ("Running %s" , " " .join (runswitch ))
771
+ ] + self .options .switch_target_options
760
772
sw = subprocess .Popen (runswitch , cwd = self .folder )
761
773
762
774
def openInterface (ifname ):
763
- fp = self .interfaces [interface ] = scapy_util .RawPcapWriter (ifname , linktype = 0 )
775
+ fp = self .interfaces [interface ] = scapy_util .RawPcapWriter (str ( ifname ) , linktype = 0 )
764
776
fp ._write_header (None )
765
777
766
778
# Try to open input interfaces. Each time, we set a 2 second
@@ -845,16 +857,18 @@ def openInterface(ifname):
845
857
testutils .log .info ("Execution completed" )
846
858
return rv
847
859
848
- def showLog (self ):
849
- with open (self .folder + "/" + self .switchLogFile + ".txt" ) as a :
860
+ def showLog (self ) -> None :
861
+ """Show the log file"""
862
+ folder = self .folder .joinpath (self .switchLogFile ).with_suffix (".txt" )
863
+ with folder .open () as a :
850
864
log = a .read ()
851
865
testutils .log .info ("Log file:\n %s" , log )
852
866
853
- def checkOutputs (self ):
867
+ def checkOutputs (self ) -> int :
854
868
"""Checks if the output of the filter matches expectations"""
855
869
testutils .log .info ("Comparing outputs" )
856
870
direction = "out"
857
- for file in glob (self .filename ("*" , direction )):
871
+ for file in glob (str ( self .filename ("*" , direction ) )):
858
872
testutils .log .info ("Checking file %s" , file )
859
873
interface = self .interface_of_filename (file )
860
874
if os .stat (file ).st_size == 0 :
@@ -907,8 +921,8 @@ def checkOutputs(self):
907
921
testutils .log .info ("All went well." )
908
922
return testutils .SUCCESS
909
923
910
- def parse_stf_file (self , testfile ) :
911
- with open (testfile ) as raw_stf :
924
+ def parse_stf_file (self , testfile : Path ) -> Tuple [ Dict [ str , str ], int ] :
925
+ with testfile . open () as raw_stf :
912
926
parser = Bmv2StfParser ()
913
927
stf_str = raw_stf .read ()
914
928
return parser .parse (stf_str )
0 commit comments