1616
1717LOGGER = logger .get_logger ('gateway' )
1818
19+ DEFAULT_TEST_HOSTS = 8
1920
2021class BaseGateway (ABC ):
2122 """Gateway collection class for managing testing services"""
2223
2324 GATEWAY_OFFSET = 0
2425 FAKE_HOST_OFFSET = 1
2526 TEST_OFFSET_START = 2
26- NUM_SET_PORTS = 6
27- SET_SPACING = 10
2827 _PING_RETRY_COUNT = 5
2928
3029 TEST_IP_FORMAT = '192.168.84.%d'
@@ -45,6 +44,10 @@ def __init__(self, runner, name, port_set, env_params=None):
4544 self .activated = False
4645 self .result_linger = False
4746 self .dhcp_monitor = None
47+ is_native = bool (self .runner .run_trigger .get ('native_vlan' ))
48+ max_hosts = self .runner .run_trigger .get ('max_hosts' ) or DEFAULT_TEST_HOSTS
49+ num_host_ports = max_hosts if is_native else DEFAULT_TEST_HOSTS
50+ self ._gw_set_size = num_host_ports + self .TEST_OFFSET_START
4851 self ._env_params = env_params
4952 self ._ext_intf = env_params .get ('ext_intf' ) if env_params else None
5053 self ._is_native = bool (self ._ext_intf )
@@ -136,7 +139,7 @@ def allocate_test_port(self):
136139 test_port = self ._switch_port (self .TEST_OFFSET_START )
137140 while test_port in self .test_ports :
138141 test_port = test_port + 1
139- limit_port = self ._switch_port (self .NUM_SET_PORTS )
142+ limit_port = self ._switch_port (self ._gw_set_size )
140143 assert test_port < limit_port , 'no test ports available'
141144 self .test_ports .add (test_port )
142145 return test_port
@@ -147,15 +150,10 @@ def release_test_port(self, test_port):
147150 self .test_ports .remove (test_port )
148151
149152 def _switch_port (self , offset ):
150- return self .port_set * self .SET_SPACING + offset
153+ return self .port_set * self ._gw_set_size + offset
151154
152155 def _is_target_expected (self , target ):
153- if not target :
154- return False
155- target_mac = target ['mac' ]
156- if target_mac in self .targets :
157- return True
158- return False
156+ return target and target ['mac' ] in self .targets
159157
160158 def _dhcp_callback (self , state , target , exception = None ):
161159 if exception :
@@ -175,16 +173,16 @@ def _setup_tmpdir(self, base_name):
175173 def attach_target (self , device ):
176174 """Attach the given target to this gateway; return number of attached targets."""
177175 assert device .mac not in self .targets , 'target %s already attached to gw' % device
178- LOGGER .info ('Attaching target %s to gateway group %s' ,
179- device , self .name )
176+ LOGGER .info ('Attaching target %s to gateway %s group %s' ,
177+ device , self .port_set , self . name )
180178 self .targets [device .mac ] = device
181179 return len (self .targets )
182180
183181 def detach_target (self , device ):
184182 """Detach the given target from this gateway; return number of remaining targets."""
185183 assert device .mac in self .targets , 'target %s not attached to gw' % device
186- LOGGER .info ('Detach target %s from gateway group %s: %s' ,
187- device , self .name , list (self .targets .keys ()))
184+ LOGGER .info ('Detaching target %s from gateway %s group %s: %s' ,
185+ device , self .port_set , self . name , list (self .targets .keys ()))
188186 del self .targets [device .mac ]
189187 return len (self .targets )
190188
@@ -200,10 +198,16 @@ def get_targets(self):
200198 """Return the host targets associated with this gateway"""
201199 return self .targets .values ()
202200
201+ def get_all_gw_ports (self ):
202+ """Return all ports associated with gateway"""
203+ base_port = self ._switch_port (0 )
204+ limit_port = self ._switch_port (self ._gw_set_size )
205+ return list (range (base_port , limit_port ))
206+
203207 def get_possible_test_ports (self ):
204208 """Return test ports associated with gateway"""
205209 test_port = self ._switch_port (self .TEST_OFFSET_START )
206- limit_port = self ._switch_port (self .NUM_SET_PORTS )
210+ limit_port = self ._switch_port (self ._gw_set_size )
207211 return list (range (test_port , limit_port ))
208212
209213 def terminate (self ):
@@ -233,7 +237,7 @@ def terminate(self):
233237 def _get_scan_interface (self ):
234238 return self .host , self .host_intf
235239
236- def _discover_host_hangup_callback (self , mac , log_fd , log_file , callback ):
240+ def _discover_host_hangup_callback (self , mac , log_fd , log_file , scan_callback ):
237241 def process_line (line ):
238242 sections = [section for section in line .split ('\t ' ) if section ]
239243 if len (sections ) >= 2 :
@@ -252,38 +256,55 @@ def process_line(line):
252256 if device_ip :
253257 LOGGER .info ('Host discovery for %s completed. Found ip %s.' ,
254258 mac , device_ip )
255- return callback (device_ip )
259+ return scan_callback (device_ip )
256260 LOGGER .info ('Host discovery for %s completed. Found no ip.' , mac )
257- callback (None )
261+ scan_callback (None )
258262
259- def discover_host (self , mac : str , subnets : List [ip_network ], callback : Callable ):
263+ def discover_host (self , mac : str , all_subnets : List [ip_network ], host_callback : Callable ):
260264 """Discovers a host using arp-scan in a list of subnets."""
261265 cmd = 'arp-scan --retry=2 --bandwidth=512K --interface=%s --destaddr=%s -s %s %s'
262266 host , intf = self ._get_scan_interface ()
267+ scans = self .runner .run_trigger .get ('arp_scan_count' ) or 2
268+ if scans <= 0 :
269+ return
270+ subnets = list (all_subnets )
263271 LOGGER .info ('Starting host discovery for %s' , mac )
264272
265- def hangup_callback_callback (device_ip ):
273+ def scan_callback (device_ip ):
274+ nonlocal scans
266275 if device_ip :
267- callback (device_ip )
276+ scans = 0
277+ host_callback (device_ip )
268278 else :
269279 recursive_discover ()
270280
271281 def recursive_discover ():
282+ nonlocal subnets , scans
272283 if not subnets :
273- callback (None )
274- return
284+ if scans > 0 :
285+ scans -= 1
286+ subnets = list (all_subnets )
287+ else :
288+ host_callback (None )
289+ return
275290 subnet = subnets .pop (0 )
276- address = next (subnet .hosts ())
291+ hosts = subnet .hosts ()
292+ address = next (hosts )
293+ # This handles the case where the local IP conflicts with the remote.
294+ # Not really a good general solution, but works in many cases.
295+ # TODO: Generalize a bit more to (e.g.) pick a random src IP.
296+ if scans % 2 :
297+ address = next (hosts )
277298 log_file = os .path .join (self .tmpdir , str (subnet ).replace ('/' , '_' ))
278299 log_fd = open (log_file , 'w' )
279- LOGGER .info ('Scanning subnet %s from %s for %s' , subnet , address , mac )
300+ LOGGER .info ('Scanning %s subnet %s from %s for %s' , scans , subnet , address , mac )
280301 host .cmd ('ip addr add %s/%s dev %s' % (str (address ), subnet .prefixlen , intf ))
281302 full_cmd = cmd % (intf , mac , str (address ), str (subnet ))
282- LOGGER .info ('arp-scan command: %s' , full_cmd )
283303 active_pipe = host .popen (full_cmd , stdin = DEVNULL , stdout = PIPE , env = os .environ )
284304 self .runner .monitor_stream (self .name , active_pipe .stdout , copy_to = log_fd ,
285305 hangup = lambda : self ._discover_host_hangup_callback (
286- mac , log_fd , log_file , hangup_callback_callback ))
306+ mac , log_fd , log_file , scan_callback ))
307+
287308 recursive_discover ()
288309
289310 def _ping_test (self , src , dst , src_addr = None ):
0 commit comments