1414#---------------------------------------------------------------------
1515# Global imports
1616#---------------------------------------------------------------------
17- import random
18- import time
19- import logging
20- import ptf .packet as scapy
21- import socket
22- import ptf .dataplane as dataplane
23-
24- from ptf .testutils import *
25- from ptf .mask import Mask
2617import ipaddress
27-
28- import os
2918import logging
30- import unittest
19+ import random
20+ import socket
21+ import sys
3122
3223import ptf
33- from ptf .base_tests import BaseTest
34- from ptf import config
24+ import ptf .packet as scapy
3525import ptf .dataplane as dataplane
36- import ptf .testutils as testutils
3726
38- import pprint
27+ from ptf import config
28+ from ptf .base_tests import BaseTest
29+ from ptf .mask import Mask
30+ from ptf .testutils import *
3931
4032class FibTest (BaseTest ):
4133 '''
@@ -47,21 +39,9 @@ class FibTest(BaseTest):
4739 Routes advertized by the peers have ECMP groups. The purpose of the test is to make sure
4840 that packets are forwarded through one of the ports specified in route's ECMP group.
4941
50-
5142 This class receives a text file describing the bgp routes added to the switch.
52-
5343 File contains informaiton about each bgp route which was added to the switch.
5444
55- #-----------------------------------------------------------------------
56- Format of the route_info file
57- #-----------------------------------------------------------------------
58- Example:
59- 192.168.0.65/32 02,00,01,13,14,08,04,09,03,07,06,12,11,10,15,05,
60- 20C0:A800:0:41::/64 02,00,01,13,14,08,04,09,03,07,06,12,11,10,15,05,
61-
62- Meaning:
63- Each entry describes IP prefix, and indexes of ports-members of ecmp group for the route.
64- The packet should be received from one of those ports.
6545 #-----------------------------------------------------------------------
6646
6747 The file is loaded on startup and is used to
@@ -80,22 +60,22 @@ class FibTest(BaseTest):
8060 #---------------------------------------------------------------------
8161 # Class variables
8262 #---------------------------------------------------------------------
83- PREFIX_AND_PORT_SPLITTER = " "
84- PORT_LIST_SPLITTER = ","
85- PORT_COUNT = 32
63+ PORT_COUNT = 32 # TODO: need to get the total number of ports from param
64+ EXPECTED_RANGE = 0.25 # TODO: need to get the percentage from param
8665
8766 '''
8867 Information about routes to test.
8968 '''
90- route_info = {}
91-
69+ port_list = [] # a list of lists describing ecmp/lag relationships
70+ route_list = [] # a list of route to be tested
71+ hit_dict = {} # a dict of hit count recording the number of hits per port
9272
9373 def __init__ (self ):
9474 '''
9575 @summary: constructor
9676 '''
9777 BaseTest .__init__ (self )
98- self .test_params = testutils . test_params_get ()
78+ self .test_params = test_params_get ()
9979 #---------------------------------------------------------------------
10080
10181 def setUp (self ):
@@ -104,29 +84,25 @@ def setUp(self):
10484 '''
10585 self .dataplane = ptf .dataplane_instance
10686 self .router_mac = self .test_params ['router_mac' ]
87+ self .load_route_info (self .test_params ["route_info" ])
10788 #---------------------------------------------------------------------
10889
10990 def load_route_info (self , route_info_path ):
11091 '''
111- @summary: Load route_info file into self.route_info. For details see section 'Format of the route_info file' in the summary of the class.
92+ @summary: Load route_info file
11293 @param route_info_path : Path to the file
11394 '''
11495 with open (route_info_path , 'r' ) as route_info_file :
115- for line in route_info_file :
116- line = line .strip ()
117- if (0 == len (line )):
118- continue
119- prefix_ports_pair = line .split (self .PREFIX_AND_PORT_SPLITTER )
120- port_list = prefix_ports_pair [1 ].split (self .PORT_LIST_SPLITTER )
121- self .route_info [prefix_ports_pair [0 ]]= port_list
122- return
123- #---------------------------------------------------------------------
124-
125- '''
126- For diagnostic purposes only
127- '''
128- def print_route_info (self ):
129- pprint .pprint (self .route_info )
96+ content = route_info_file .readlines ()
97+ # Parse the first line to get the port_list
98+ ecmp_list = content [0 ].split ()
99+ for ecmp_entry in ecmp_list :
100+ lag_list = ecmp_entry .split (',' )
101+ lag_list = [ int (member ) for member in lag_list ]
102+ self .port_list .append (lag_list )
103+ # Parse the reset of the file to get the route_list
104+ for line in content [1 :]:
105+ self .route_list .append (line .strip ())
130106 return
131107 #---------------------------------------------------------------------
132108
@@ -146,7 +122,7 @@ def verify_packet_any_port(self, pkt, ports=[], device_number=0):
146122 @return: index of the port on which the packet is received and the packet.
147123 """
148124 received = False
149- match_index = 0
125+ match_index = - 1
150126 (rcv_device , rcv_port , rcv_pkt , pkt_time ) = dp_poll (
151127 self ,
152128 device_number = device_number ,
@@ -155,10 +131,10 @@ def verify_packet_any_port(self, pkt, ports=[], device_number=0):
155131 )
156132
157133 if rcv_port in ports :
158- match_index = ports .index (rcv_port )
159- received = True
134+ match_index = ports .index (rcv_port )
135+ received = True
160136
161- return (match_index , rcv_pkt , received )
137+ return (match_index , received )
162138 #---------------------------------------------------------------------
163139
164140 def is_ipv4_address (self , ipaddr ):
@@ -167,15 +143,13 @@ def is_ipv4_address(self, ipaddr):
167143 @param ipaddr IP address to check
168144 @return Boolean
169145 '''
170- is_valid_ipv4 = True
171- try :
146+ try :
172147 # building ipaddress fails for some of addresses unless unicode(ipaddr) is specified for both ipv4/ipv6
173148 # Example - 192.168.156.129, it is valid IPV4 address, send_packet works with it.
174- ip = ipaddress .IPv4Address (unicode (ipaddr ))
175- except Exception , e :
176- is_valid_ipv4 = False
177-
178- return is_valid_ipv4
149+ ipaddress .IPv4Address (unicode (ipaddr ))
150+ return True
151+ except Exception , e :
152+ return False
179153 #---------------------------------------------------------------------
180154
181155 def is_ipv6_address (self , ipaddr ):
@@ -184,13 +158,11 @@ def is_ipv6_address(self, ipaddr):
184158 @param ipaddr IP address to check
185159 @return Boolean
186160 '''
187- is_valid_ipv6 = True
188- try :
189- ip = ipaddress . IPv6Address ( unicode ( ipaddr ))
161+ try :
162+ ipaddress . IPv6Address ( unicode ( ipaddr ))
163+ return True
190164 except Exception , e :
191- is_valid_ipv6 = False
192-
193- return is_valid_ipv6
165+ return False
194166 #---------------------------------------------------------------------
195167
196168 def check_ipv4_route (self , source_port_index , dest_ip_addr , destination_port_list ):
@@ -201,8 +173,8 @@ def check_ipv4_route(self, source_port_index, dest_ip_addr, destination_port_lis
201173 @param destination_port_list: list of ports on which to expect packet to come back from the switch
202174 @return Boolean
203175 '''
204- sport = 0x1234
205- dport = 0x50
176+ sport = random . randint ( 0 , 65535 )
177+ dport = random . randint ( 0 , 65535 )
206178 ip_src = "10.0.0.1"
207179 ip_dst = dest_ip_addr
208180
@@ -217,7 +189,6 @@ def check_ipv4_route(self, source_port_index, dest_ip_addr, destination_port_lis
217189 tcp_dport = dport ,
218190 ip_ttl = 64 )
219191 exp_pkt = simple_tcp_packet (
220- eth_dst = self .dataplane .get_mac (0 , 0 ),
221192 eth_src = self .router_mac ,
222193 ip_src = ip_src ,
223194 ip_dst = ip_dst ,
@@ -226,18 +197,10 @@ def check_ipv4_route(self, source_port_index, dest_ip_addr, destination_port_lis
226197 ip_ttl = 63 )
227198 masked_exp_pkt = Mask (exp_pkt )
228199 masked_exp_pkt .set_do_not_care_scapy (scapy .Ether ,"dst" )
229- masked_exp_pkt .set_do_not_care_scapy (scapy .Ether ,"src" )
230200
231- result = False
232201 send_packet (self , source_port_index , pkt )
233202
234- (match_index ,rcv_pkt , received ) = self .verify_packet_any_port (masked_exp_pkt ,destination_port_list )
235- if received :
236- result = True
237- else :
238- print 'FAIL for ip:%s' % dest_ip_addr ,
239- pprint .pprint (destination_port_list )
240- return result
203+ return self .verify_packet_any_port (masked_exp_pkt ,destination_port_list )
241204 #---------------------------------------------------------------------
242205
243206 def check_ipv6_route (self , source_port_index , dest_ip_addr , destination_port_list ):
@@ -248,8 +211,8 @@ def check_ipv6_route(self, source_port_index, dest_ip_addr, destination_port_lis
248211 @param destination_port_list: list of ports on which to expect packet to come back from the switch
249212 @return Boolean
250213 '''
251- sport = 0x2233
252- dport = 0x60
214+ sport = random . randint ( 0 , 65535 )
215+ dport = random . randint ( 0 , 65535 )
253216 ip_src = '2000::1'
254217 ip_dst = dest_ip_addr
255218
@@ -264,70 +227,106 @@ def check_ipv6_route(self, source_port_index, dest_ip_addr, destination_port_lis
264227 tcp_dport = dport ,
265228 ipv6_hlim = 64 )
266229 exp_pkt = simple_tcpv6_packet (
267- eth_dst = self .dataplane .get_mac (0 , 0 ),
268- eth_src = src_mac ,
230+ eth_src = self .router_mac ,
269231 ipv6_dst = ip_dst ,
270232 ipv6_src = ip_src ,
271233 tcp_sport = sport ,
272234 tcp_dport = dport ,
273235 ipv6_hlim = 63 )
274236 masked_exp_pkt = Mask (exp_pkt )
275237 masked_exp_pkt .set_do_not_care_scapy (scapy .Ether ,"dst" )
276- masked_exp_pkt .set_do_not_care_scapy (scapy .Ether ,"src" )
277238
278- result = False
279239 send_packet (self , source_port_index , pkt )
280240
281- (match_index ,rcv_pkt , received ) = self .verify_packet_any_port (masked_exp_pkt ,destination_port_list )
282-
283- if received :
284- result = True
285- else :
286- print 'src_port:%d' % source_port_index ,
287- print 'FAIL for ip:%s' % dest_ip_addr ,
288- pprint .pprint (destination_port_list )
241+ return self .verify_packet_any_port (masked_exp_pkt ,destination_port_list )
242+ #---------------------------------------------------------------------
243+ def check_within_expected_range (self , actual , expected ):
244+ '''
245+ @summary: Check if the actual number is within the accepted range of the expected number
246+ @param actual : acutal number of recieved packets
247+ @param expected : expected number of recieved packets
248+ @return (percentage, bool)
249+ '''
250+ percentage = abs (actual - expected ) / float (expected )
251+ return (percentage , percentage <= self .EXPECTED_RANGE )
252+
253+ #---------------------------------------------------------------------
254+ def check_balancing (self , port_list , port_hit_cnt ):
255+ '''
256+ @summary: Check if the traffic is balanced across the ECMP groups and the LAG members
257+ @param port_list : a list of ECMP entries and in each ECMP entry a list of ports
258+ @param port_hit_cnt : a dict that records the number of packets each port received
259+ @return bool
260+ '''
261+
262+ logging .debug ("%-10s \t %10s \t %10s \t %10s" % ("port(s)" , "exp_cnt" , "act_cnt" , "diff(%)" ))
263+ result = True
264+
265+ total_hit_cnt = float (sum (port_hit_cnt .values ()))
266+ for ecmp_entry in port_list :
267+ total_entry_hit_cnt = 0.0
268+ for member in ecmp_entry :
269+ total_entry_hit_cnt += port_hit_cnt [member ]
270+ (p , r ) = self .check_within_expected_range (total_entry_hit_cnt , total_hit_cnt / len (port_list ))
271+ logging .debug ("%-10s \t %10d \t %10d \t %10s"
272+ % (str (ecmp_entry ), total_hit_cnt / len (port_list ), total_entry_hit_cnt , str (round (p , 4 )* 100 ) + '%' ))
273+ result &= r
274+ for member in ecmp_entry :
275+ (p , r ) = self .check_within_expected_range (port_hit_cnt [member ], total_entry_hit_cnt / len (ecmp_entry ))
276+ logging .debug ("%-10s \t %10d \t %10d \t %10s"
277+ % (str (member ), total_entry_hit_cnt / len (ecmp_entry ), port_hit_cnt [member ], str (round (p , 4 )* 100 ) + '%' ))
278+ result &= r
289279 return result
280+
290281 #---------------------------------------------------------------------
291-
282+
292283 def runTest (self ):
293284 """
294285 @summary: Send packet for each route and validate it arrives
295286 on one of expected ECMP ports
296287 """
297- self .load_route_info (self .test_params ["route_info" ])
298- pass_count = 0
299- test_result = True
300- result = True
288+ exp_port_list = []
289+ for ecmp_entry in self .port_list :
290+ for port in ecmp_entry :
291+ exp_port_list .append (port )
292+
301293 ip4_route_cnt = 0
302294 ip6_route_cnt = 0
303-
304- for prefix , port_index_list in self .route_info .iteritems () :
305- dest_ip_addr = prefix .split ("/" )[0 ]
306- destination_port_list = []
307- for port_index in port_index_list :
308- if len (port_index ) > 0 :
309- destination_port_list .append (int (port_index ))
310-
295+ ip4_hit_cnt = 0
296+ ip6_hit_cnt = 0
297+ port_cnt_dict = {}
298+
299+ x = 0
300+ for dest_ip in self .route_list :
311301 for src_port in xrange (0 , self .PORT_COUNT ):
312-
313- if src_port in destination_port_list : continue
314-
315- if self .is_ipv4_address (dest_ip_addr ):
302+ if self .is_ipv4_address (dest_ip ):
316303 ip4_route_cnt += 1
317- result = self .check_ipv4_route (src_port , dest_ip_addr , destination_port_list )
318- elif self .is_ipv6_address (dest_ip_addr ):
304+ (matched_index , received ) = self .check_ipv4_route (src_port , dest_ip , exp_port_list )
305+ if received :
306+ ip4_hit_cnt += 1
307+ port_cnt_dict [exp_port_list [matched_index ]] = port_cnt_dict .setdefault (exp_port_list [matched_index ], 0 ) + 1
308+ elif self .is_ipv6_address (dest_ip ):
319309 ip6_route_cnt += 1
320- result = self .check_ipv6_route (src_port , dest_ip_addr , destination_port_list )
310+ (matched_index , received ) = self .check_ipv6_route (src_port , dest_ip , exp_port_list )
311+ if received :
312+ ip6_hit_cnt += 1
313+ port_cnt_dict [exp_port_list [matched_index ]] = port_cnt_dict .setdefault (exp_port_list [matched_index ], 0 ) + 1
321314 else :
322- print 'Invalid ip address:%s' % dest_ip_addr
315+ print 'Invalid IP address:%s' % dest_ip_addr
323316 assert (False )
324317
325- test_result = test_result and result
326- if (result ):
327- pass_count = pass_count + 1
328-
329- print 'pass_count:%d' % pass_count
330- print 'ip4_route_cnt:%d' % ip4_route_cnt
331- print 'ip6_route_cnt:%d' % ip6_route_cnt
332- assert (test_result )
318+ ch = logging .StreamHandler (sys .stdout )
319+ # Modify the logging level to enable debugs
320+ ch .setLevel (logging .INFO )
321+ ch .terminator = ""
322+ logging .getLogger ().addHandler (ch )
323+
324+ # Check if sent/received counts are matched
325+ logging .debug ("\n " )
326+ logging .debug ("--------------------------- TEST RESULT ------------------------------" )
327+ logging .debug ("Sent %d IPv4 packets; recieved %d IPv4 packets" % (ip4_route_cnt , ip4_hit_cnt ))
328+ logging .debug ("Sent %d IPv6 packets; recieved %d IPv6 packets" % (ip6_route_cnt , ip6_hit_cnt ))
329+ logging .debug ("----------------------------------------------------------------------" )
330+ balancing_result = self .check_balancing (self .port_list , port_cnt_dict )
331+ assert (ip4_route_cnt == ip4_hit_cnt ) and (ip6_route_cnt == ip6_hit_cnt ) and balancing_result
333332 #---------------------------------------------------------------------
0 commit comments