blob: daa30e835597a8b5a0adaf774ae17a57cdb9f6f7 [file] [log] [blame]
You Wang84f981d2018-01-12 16:11:50 -08001#!/usr/bin/env python
2"""
3Copyright 2018 Open Networking Foundation (ONF)
4
5Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
6the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
7or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
8
9 TestON is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 2 of the License, or
12 (at your option) any later version.
13
14 TestON is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with TestON. If not, see <http://www.gnu.org/licenses/>.
21
22
23This driver is used to interact with a physical network that SDN controller is controlling.
24
25Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
26the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
27or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
28
29"""
30import pexpect
31import os
You Wang4cc61912018-08-28 10:10:58 -070032import re
You Wang84f981d2018-01-12 16:11:50 -080033import types
Jon Hall43060f62020-06-23 13:13:33 -070034import time
35import itertools
36import random
You Wang84f981d2018-01-12 16:11:50 -080037from drivers.common.clidriver import CLI
You Wangb1665b52019-02-01 15:49:48 -080038from core.graph import Graph
You Wang84f981d2018-01-12 16:11:50 -080039
40class NetworkDriver( CLI ):
41
42 def __init__( self ):
43 """
44 switches: a dictionary that maps switch names to components
45 hosts: a dictionary that maps host names to components
46 """
47 self.name = None
48 self.home = None
49 self.handle = None
50 self.switches = {}
51 self.hosts = {}
You Wangb1665b52019-02-01 15:49:48 -080052 self.links = {}
You Wang84f981d2018-01-12 16:11:50 -080053 super( NetworkDriver, self ).__init__()
You Wangb1665b52019-02-01 15:49:48 -080054 self.graph = Graph()
You Wang84f981d2018-01-12 16:11:50 -080055
You Wang84f981d2018-01-12 16:11:50 -080056 def connect( self, **connectargs ):
57 """
58 Creates ssh handle for the SDN network "bench".
59 NOTE:
60 The ip_address would come from the topo file using the host tag, the
61 value can be an environment variable as well as a "localhost" to get
62 the ip address needed to ssh to the "bench"
63 """
64 try:
65 for key in connectargs:
66 vars( self )[ key ] = connectargs[ key ]
67 self.name = self.options[ 'name' ]
68 try:
69 if os.getenv( str( self.ip_address ) ) is not None:
70 self.ip_address = os.getenv( str( self.ip_address ) )
71 else:
72 main.log.info( self.name +
73 ": Trying to connect to " +
74 self.ip_address )
75 except KeyError:
76 main.log.info( "Invalid host name," +
77 " connecting to local host instead" )
78 self.ip_address = 'localhost'
79 except Exception as inst:
80 main.log.error( "Uncaught exception: " + str( inst ) )
81
82 self.handle = super( NetworkDriver, self ).connect(
83 user_name=self.user_name,
84 ip_address=self.ip_address,
85 port=self.port,
86 pwd=self.pwd )
87
88 if self.handle:
89 main.log.info( "Connected to network bench node" )
90 return self.handle
91 else:
92 main.log.info( "Failed to create handle" )
93 return main.FALSE
94 except pexpect.EOF:
95 main.log.error( self.name + ": EOF exception found" )
96 main.log.error( self.name + ": " + self.handle.before )
97 main.cleanAndExit()
98 except Exception:
99 main.log.exception( self.name + ": Uncaught exception!" )
100 main.cleanAndExit()
101
102 def disconnect( self ):
103 """
104 Called when test is complete to disconnect the handle.
105 """
106 response = main.TRUE
107 try:
108 if self.handle:
109 self.handle.sendline( "exit" )
110 self.handle.expect( "closed" )
111 except pexpect.EOF:
112 main.log.error( self.name + ": EOF exception found" )
113 main.log.error( self.name + ": " + self.handle.before )
114 except Exception:
115 main.log.exception( self.name + ": Connection failed to the host" )
116 response = main.FALSE
117 return response
118
119 def connectToNet( self ):
120 """
121 Connect to an existing physical network by getting information
122 of all switch and host components created
123 """
124 try:
125 for key, value in main.componentDictionary.items():
126 if hasattr( main, key ):
Jon Hall43060f62020-06-23 13:13:33 -0700127 if value[ 'type' ] in [ 'MininetSwitchDriver', 'OFDPASwitchDriver', 'StratumOSSwitchDriver' ]:
You Wang4cc61912018-08-28 10:10:58 -0700128 component = getattr( main, key )
129 shortName = component.options[ 'shortName' ]
130 localName = self.name + "-" + shortName
131 self.copyComponent( key, localName )
132 self.switches[ shortName ] = getattr( main, localName )
Pier6a0c4de2018-03-18 16:01:30 -0700133 elif value[ 'type' ] in [ 'MininetHostDriver', 'HostDriver' ]:
You Wang4cc61912018-08-28 10:10:58 -0700134 component = getattr( main, key )
135 shortName = component.options[ 'shortName' ]
136 localName = self.name + "-" + shortName
137 self.copyComponent( key, localName )
138 self.hosts[ shortName ] = getattr( main, localName )
139 main.log.debug( self.name + ": found switches: {}".format( self.switches ) )
140 main.log.debug( self.name + ": found hosts: {}".format( self.hosts ) )
You Wang84f981d2018-01-12 16:11:50 -0800141 return main.TRUE
142 except Exception:
143 main.log.error( self.name + ": failed to connect to network" )
144 return main.FALSE
145
You Wang4cc61912018-08-28 10:10:58 -0700146 def disconnectFromNet( self ):
You Wang84f981d2018-01-12 16:11:50 -0800147 """
You Wang4cc61912018-08-28 10:10:58 -0700148 Disconnect from the physical network connected
You Wang84f981d2018-01-12 16:11:50 -0800149 """
You Wang84f981d2018-01-12 16:11:50 -0800150 try:
You Wang4cc61912018-08-28 10:10:58 -0700151 for key, value in main.componentDictionary.items():
152 if hasattr( main, key ) and key.startswith( self.name + "-" ):
153 self.removeComponent( key )
154 self.switches = {}
155 self.hosts = {}
156 return main.TRUE
You Wang84f981d2018-01-12 16:11:50 -0800157 except Exception:
You Wang4cc61912018-08-28 10:10:58 -0700158 main.log.error( self.name + ": failed to disconnect from network" )
159 return main.FALSE
160
161 def copyComponent( self, name, newName ):
162 """
163 Copy the component initialized from the .topo file
164 The copied components are only supposed to be called within this driver
165 Required:
166 name: name of the component to be copied
167 newName: name of the new component
168 """
169 try:
170 main.componentDictionary[ newName ] = main.componentDictionary[ name ].copy()
171 main.componentInit( newName )
172 except Exception:
173 main.log.exception( self.name + ": Uncaught exception!" )
174 main.cleanAndExit()
175
176 def removeHostComponent( self, name ):
177 """
178 Remove host component
179 Required:
180 name: name of the component to be removed
181 """
182 try:
183 self.removeComponent( name )
184 except Exception:
185 main.log.exception( self.name + ": Uncaught exception!" )
186 main.cleanAndExit()
187
188 def removeComponent( self, name ):
189 """
190 Remove host/switch component
191 Required:
192 name: name of the component to be removed
193 """
194 try:
195 component = getattr( main, name )
196 except AttributeError:
197 main.log.error( "Component " + name + " does not exist." )
198 return main.FALSE
199 try:
200 # Disconnect from component
201 component.disconnect()
202 # Delete component
203 delattr( main, name )
204 # Delete component from ComponentDictionary
205 del( main.componentDictionary[ name ] )
206 return main.TRUE
207 except Exception:
208 main.log.exception( self.name + ": Uncaught exception!" )
209 main.cleanAndExit()
210
You Wang0fc21702018-11-02 17:49:18 -0700211 def createHostComponent( self, name ):
You Wang4cc61912018-08-28 10:10:58 -0700212 """
You Wang0fc21702018-11-02 17:49:18 -0700213 Creates host component with the same parameters as the one copied to local.
You Wang4cc61912018-08-28 10:10:58 -0700214 Arguments:
215 name - The string of the name of this component. The new component
216 will be assigned to main.<name> .
217 In addition, main.<name>.name = str( name )
218 """
219 try:
220 # look to see if this component already exists
221 getattr( main, name )
222 except AttributeError:
223 # namespace is clear, creating component
224 localName = self.name + "-" + name
225 main.componentDictionary[ name ] = main.componentDictionary[ localName ].copy()
226 main.componentInit( name )
227 except Exception:
228 main.log.exception( self.name + ": Uncaught exception!" )
229 main.cleanAndExit()
230 else:
231 # namespace is not clear!
232 main.log.error( name + " component already exists!" )
233 main.cleanAndExit()
234
235 def connectInbandHosts( self ):
236 """
237 Connect to hosts using data plane IPs specified
238 """
239 result = main.TRUE
240 try:
241 for hostName, hostComponent in self.hosts.items():
242 if hostComponent.options[ 'inband' ] == 'True':
243 main.log.info( self.name + ": connecting inband host " + hostName )
244 result = hostComponent.connectInband() and result
245 return result
246 except Exception:
247 main.log.error( self.name + ": failed to connect to inband hosts" )
248 return main.FALSE
249
250 def disconnectInbandHosts( self ):
251 """
252 Terminate the connections to hosts using data plane IPs
253 """
254 result = main.TRUE
255 try:
256 for hostName, hostComponent in self.hosts.items():
257 if hostComponent.options[ 'inband' ] == 'True':
258 main.log.info( self.name + ": disconnecting inband host " + hostName )
259 result = hostComponent.disconnectInband() and result
260 return result
261 except Exception:
262 main.log.error( self.name + ": failed to disconnect inband hosts" )
263 return main.FALSE
264
You Wangb1665b52019-02-01 15:49:48 -0800265 def getSwitches( self, timeout=60, excludeNodes=[], includeStopped=False ):
You Wang4cc61912018-08-28 10:10:58 -0700266 """
You Wangb1665b52019-02-01 15:49:48 -0800267 Return a dictionary which maps short names to switch data
268 If includeStopped is True, stopped switches will also be included
You Wang4cc61912018-08-28 10:10:58 -0700269 """
You Wangb1665b52019-02-01 15:49:48 -0800270 switches = {}
271 for switchName, switchComponent in self.switches.items():
272 if switchName in excludeNodes:
273 continue
274 if not includeStopped and not switchComponent.isup:
275 continue
Jon Hall43060f62020-06-23 13:13:33 -0700276 try:
277 dpid = switchComponent.dpid
278 except AttributeError:
279 main.log.warn( "Switch has no dpid, ignore this if not an OpenFlow switch" )
280 dpid = "0x0"
281 dpid = dpid.replace( '0x', '' ).zfill( 16 )
You Wangb1665b52019-02-01 15:49:48 -0800282 ports = switchComponent.ports
283 swClass = 'Unknown'
284 pid = None
285 options = None
286 switches[ switchName ] = { "dpid": dpid,
287 "ports": ports,
288 "swClass": swClass,
289 "pid": pid,
290 "options": options }
291 return switches
You Wang4cc61912018-08-28 10:10:58 -0700292
You Wangb1665b52019-02-01 15:49:48 -0800293 def getHosts( self, hostClass=None ):
You Wang4cc61912018-08-28 10:10:58 -0700294 """
You Wangb1665b52019-02-01 15:49:48 -0800295 Return a dictionary which maps short names to host data
You Wang4cc61912018-08-28 10:10:58 -0700296 """
You Wangb1665b52019-02-01 15:49:48 -0800297 hosts = {}
298 for hostName, hostComponent in self.hosts.items():
299 interfaces = hostComponent.interfaces
300 hosts[ hostName ] = { "interfaces": interfaces }
301 return hosts
302
303 def updateLinks( self, timeout=60, excludeNodes=[] ):
304 """
305 Update self.links by getting up-to-date port information from
306 switches
307 """
308 # TODO: also inlcude switch-to-host links
309 self.links = {}
310 for node1 in self.switches.keys():
311 if node1 in excludeNodes:
312 continue
313 self.links[ node1 ] = {}
314 self.switches[ node1 ].updatePorts()
315 for port in self.switches[ node1 ].ports:
316 if not port[ 'enabled' ]:
317 continue
318 node2 = getattr( main, port[ 'node2' ] ).shortName
319 if node2 in excludeNodes:
320 continue
321 port1 = port[ 'of_port' ]
322 port2 = port[ 'port2' ]
323 if not self.links[ node1 ].get( node2 ):
324 self.links[ node1 ][ node2 ] = {}
325 # Check if this link already exists
326 if self.links.get( node2 ):
327 if self.links[ node2 ].get( node1 ):
328 if self.links[ node2 ].get( node1 ).get( port2 ):
329 assert self.links[ node2 ][ node1 ][ port2 ] == port1
330 continue
331 self.links[ node1 ][ node2 ][ port1 ] = port2
332
333 def getLinks( self, timeout=60, excludeNodes=[] ):
334 """
335 Return a list of links specify both node names and port numbers
336 """
337 self.updateLinks( timeout=timeout, excludeNodes=excludeNodes )
338 links = []
339 for node1, nodeLinks in self.links.items():
340 for node2, ports in nodeLinks.items():
341 for port1, port2 in ports.items():
342 links.append( { 'node1': node1, 'node2': node2,
343 'port1': port1, 'port2': port2 } )
344 return links
You Wang84f981d2018-01-12 16:11:50 -0800345
346 def getMacAddress( self, host ):
347 """
348 Return MAC address of a host
349 """
You Wang84f981d2018-01-12 16:11:50 -0800350 try:
You Wangb1665b52019-02-01 15:49:48 -0800351 hostComponent = self.hosts[ host ]
You Wang84f981d2018-01-12 16:11:50 -0800352 response = hostComponent.ifconfig()
353 pattern = r'HWaddr\s([0-9A-F]{2}[:-]){5}([0-9A-F]{2})'
354 macAddressSearch = re.search( pattern, response, re.I )
355 macAddress = macAddressSearch.group().split( " " )[ 1 ]
356 main.log.info( self.name + ": Mac-Address of Host " + host + " is " + macAddress )
357 return macAddress
358 except Exception:
359 main.log.error( self.name + ": failed to get host MAC address" )
360
You Wang4cc61912018-08-28 10:10:58 -0700361 def runCmdOnHost( self, hostName, cmd ):
You Wang84f981d2018-01-12 16:11:50 -0800362 """
You Wang4cc61912018-08-28 10:10:58 -0700363 Run shell command on specified host and return output
364 Required:
365 hostName: name of the host e.g. "h1"
366 cmd: command to run on the host
You Wang84f981d2018-01-12 16:11:50 -0800367 """
You Wangb1665b52019-02-01 15:49:48 -0800368 hostComponent = self.hosts[ hostName ]
You Wang4cc61912018-08-28 10:10:58 -0700369 if hostComponent:
370 return hostComponent.command( cmd )
You Wang84f981d2018-01-12 16:11:50 -0800371 return None
372
373 def assignSwController( self, sw, ip, port="6653", ptcp="" ):
374 """
375 Description:
376 Assign switches to the controllers
377 Required:
378 sw - Short name of the switch specified in the .topo file, e.g. "s1".
379 It can also be a list of switch names.
380 ip - Ip addresses of controllers. This can be a list or a string.
381 Optional:
382 port - ONOS use port 6653, if no list of ports is passed, then
383 the all the controller will use 6653 as their port number
384 ptcp - ptcp number, This can be a string or a list that has
385 the same length as switch. This is optional and not required
386 when using ovs switches.
387 NOTE: If switches and ptcp are given in a list type they should have the
388 same length and should be in the same order, Eg. sw=[ 's1' ... n ]
389 ptcp=[ '6637' ... n ], s1 has ptcp number 6637 and so on.
390
391 Return:
392 Returns main.TRUE if switches are correctly assigned to controllers,
393 otherwise it will return main.FALSE or an appropriate exception(s)
394 """
395 switchList = []
396 ptcpList = None
397 try:
398 if isinstance( sw, types.StringType ):
399 switchList.append( sw )
400 if ptcp:
401 if isinstance( ptcp, types.StringType ):
402 ptcpList = [ ptcp ]
403 elif isinstance( ptcp, types.ListType ):
404 main.log.error( self.name + ": Only one switch is " +
405 "being set and multiple PTCP is " +
406 "being passed " )
407 return main.FALSE
408 else:
409 main.log.error( self.name + ": Invalid PTCP" )
410 return main.FALSE
411
412 elif isinstance( sw, types.ListType ):
413 switchList = sw
414 if ptcp:
415 if isinstance( ptcp, types.ListType ):
416 if len( ptcp ) != len( sw ):
417 main.log.error( self.name + ": PTCP length = " +
418 str( len( ptcp ) ) +
419 " is not the same as switch" +
420 " length = " +
421 str( len( sw ) ) )
422 return main.FALSE
423 else:
424 ptcpList = ptcp
425 else:
426 main.log.error( self.name + ": Invalid PTCP" )
427 return main.FALSE
428 else:
429 main.log.error( self.name + ": Invalid switch type " )
430 return main.FALSE
431
432 assignResult = main.TRUE
433 index = 0
434 for switch in switchList:
435 assigned = False
You Wang4cc61912018-08-28 10:10:58 -0700436 switchComponent = self.switches[ switch ]
You Wang84f981d2018-01-12 16:11:50 -0800437 if switchComponent:
438 ptcp = ptcpList[ index ] if ptcpList else ""
439 assignResult = assignResult and switchComponent.assignSwController( ip=ip, port=port, ptcp=ptcp )
440 assigned = True
441 if not assigned:
442 main.log.error( self.name + ": Not able to find switch " + switch )
443 assignResult = main.FALSE
444 index += 1
445 return assignResult
446
447 except Exception:
448 main.log.exception( self.name + ": Uncaught exception!" )
449 main.cleanAndExit()
450
451 def pingall( self, protocol="IPv4", timeout=300, shortCircuit=False, acceptableFailed=0 ):
452 """
453 Description:
454 Verifies the reachability of the hosts using ping command.
455 Optional:
456 protocol - use ping6 command if specified as "IPv6"
457 timeout( seconds ) - How long to wait before breaking the pingall
458 shortCircuit - Break the pingall based on the number of failed hosts ping
459 acceptableFailed - Set the number of acceptable failed pings for the
460 function to still return main.TRUE
461 Returns:
462 main.TRUE if pingall completes with no pings dropped
463 otherwise main.FALSE
464 """
You Wang84f981d2018-01-12 16:11:50 -0800465 try:
466 timeout = int( timeout )
467 main.log.info( self.name + ": Checking reachabilty to the hosts using ping" )
468 failedPings = 0
469 returnValue = main.TRUE
470 ipv6 = True if protocol == "IPv6" else False
471 startTime = time.time()
You Wangb1665b52019-02-01 15:49:48 -0800472 hostPairs = itertools.permutations( list( self.hosts.values() ), 2 )
You Wang84f981d2018-01-12 16:11:50 -0800473 for hostPair in list( hostPairs ):
474 ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ]
475 pingResult = hostPair[ 0 ].ping( ipDst, ipv6=ipv6 )
476 returnValue = returnValue and pingResult
477 if ( time.time() - startTime ) > timeout:
478 returnValue = main.FALSE
479 main.log.error( self.name +
480 ": Aborting pingall - " +
481 "Function took too long " )
482 break
483 if not pingResult:
484 failedPings = failedPings + 1
485 if failedPings > acceptableFailed:
486 returnValue = main.FALSE
487 if shortCircuit:
488 main.log.error( self.name +
489 ": Aborting pingall - "
490 + str( failedPings ) +
491 " pings failed" )
492 break
493 return returnValue
494 except Exception:
495 main.log.exception( self.name + ": Uncaught exception!" )
496 main.cleanAndExit()
497
Siddesh559d7412021-10-01 06:01:55 +0000498 def pingallHosts( self, hostList, ipv6=False, wait=1, useScapy=False, returnResult=False ):
You Wang84f981d2018-01-12 16:11:50 -0800499 """
500 Ping all specified IPv4 hosts
501
502 Acceptable hostList:
503 - [ 'h1','h2','h3','h4' ]
504
505 Returns main.TRUE if all hosts specified can reach
506 each other
507
508 Returns main.FALSE if one or more of hosts specified
509 cannot reach each other"""
You Wang84f981d2018-01-12 16:11:50 -0800510 hostComponentList = []
511 for hostName in hostList:
Jon Hall06fd0df2021-01-25 15:50:06 -0800512 hostComponent = self.hosts[ str( hostName ) ]
You Wang84f981d2018-01-12 16:11:50 -0800513 if hostComponent:
514 hostComponentList.append( hostComponent )
515 try:
516 main.log.info( "Testing reachability between specified hosts" )
517 isReachable = main.TRUE
518 pingResponse = "IPv4 ping across specified hosts\n"
519 failedPings = 0
520 hostPairs = itertools.permutations( list( hostComponentList ), 2 )
Siddesh559d7412021-10-01 06:01:55 +0000521 resultList = []
You Wang84f981d2018-01-12 16:11:50 -0800522 for hostPair in list( hostPairs ):
Jon Hall43060f62020-06-23 13:13:33 -0700523 ipDst = hostPair[ 1 ].options.get( 'ip6', hostPair[ 1 ].options[ 'ip' ] ) if ipv6 else hostPair[ 1 ].options[ 'ip' ]
524 srcIface = hostPair[ 0 ].interfaces[0].get( 'name' )
525 dstIface = hostPair[ 1 ].interfaces[0].get( 'name' )
526 srcMac = hostPair[0].interfaces[0].get( 'mac' )
527 dstMac = hostPair[1].interfaces[0].get( 'mac' )
528 if useScapy:
529 main.log.debug( "Pinging from " + str( hostPair[ 0 ].shortName ) + " to " + str( hostPair[ 1 ].shortName ) )
530 srcIPs = hostPair[ 0 ].interfaces[0].get( 'ips' )
531 dstIPs = hostPair[ 1 ].interfaces[0].get( 'ips' )
Siddeshde1d1692021-09-15 18:12:57 +0000532 srcVLANs = hostPair[0].interfaces[0].get( 'vlan', [None] )
533 main.log.debug( srcVLANs )
534 for VLAN in srcVLANs:
535 pingResponse += hostPair[ 0 ].options[ 'shortName' ]
Siddesh559d7412021-10-01 06:01:55 +0000536 # returnResult.append({ "src": hostPair[0].shortName, "vlan": VLAN, "dst": hostPair[1].shortName, "result": pingResult})
Siddeshde1d1692021-09-15 18:12:57 +0000537 if VLAN:
538 pingResponse += "." + str( VLAN )
539 pingResponse += " -> "
540 main.log.debug( VLAN )
541 dstVLANs = hostPair[1].interfaces[0].get( 'vlan' )
542 # Use scapy to send and recieve packets
543 hostPair[ 1 ].startScapy( ifaceName=dstIface )
Siddeshde1d1692021-09-15 18:12:57 +0000544 filters = []
545 if srcMac:
546 filters.append( "ether src host %s" % srcMac )
547 if srcIPs[0]:
548 filters.append( "ip src host %s" % srcIPs[0] )
549 hostPair[ 1 ].startFilter( ifaceName=dstIface, pktFilter=" and ".join(filters) )
550 hostPair[ 0 ].startScapy( ifaceName=srcIface )
Siddeshde1d1692021-09-15 18:12:57 +0000551 hostPair[ 0 ].buildEther( src=srcMac, dst=dstMac )
552 if VLAN:
553 hostPair[ 0 ].buildVLAN( vlan=VLAN )
554 hostPair[ 0 ].buildIP( src=srcIPs[0], dst=dstIPs[0] )
555 hostPair[ 0 ].buildICMP( vlan=VLAN )
556 hostPair[ 0 ].sendPacket( iface=srcIface )
Jon Hall43060f62020-06-23 13:13:33 -0700557
Siddeshde1d1692021-09-15 18:12:57 +0000558 waiting = not hostPair[ 1 ].checkFilter()
559 if not waiting:
560 pingResult = main.FALSE
561 packets = hostPair[ 1 ].readPackets()
562 main.log.warn( repr( packets ) )
563 for packet in packets.splitlines():
564 main.log.debug( packet )
565 if srcIPs[0] in packet:
566 pingResult = main.TRUE
567 else:
568 main.log.warn( "Did not receive packets, killing filter" )
569 kill = hostPair[ 1 ].killFilter()
570 main.log.debug( kill )
571 hostPair[ 1 ].handle.sendline( "" )
572 hostPair[ 1 ].handle.expect( hostPair[ 1 ].scapyPrompt )
573 main.log.debug( hostPair[ 1 ].handle.before )
574 # One of the host to host pair is unreachable
575 pingResult = main.FALSE
Siddesh559d7412021-10-01 06:01:55 +0000576 resultList.append({ "src": hostPair[0].shortName, "vlan": str(VLAN), "dst": hostPair[1].shortName, "result": pingResult})
Siddeshde1d1692021-09-15 18:12:57 +0000577 hostPair[ 0 ].stopScapy()
578 hostPair[ 1 ].stopScapy()
579 if pingResult:
580 pingResponse += hostPair[ 1 ].options[ 'shortName' ]
581 if VLAN:
582 pingResponse += "." + str( VLAN )
583 else:
584 pingResponse += "X"
585 # One of the host to host pair is unreachable
586 isReachable = main.FALSE
587 failedPings += 1
588 pingResponse += "\n"
Jon Hall43060f62020-06-23 13:13:33 -0700589 else:
Siddeshde1d1692021-09-15 18:12:57 +0000590 pingResponse += hostPair[ 0 ].options[ 'shortName' ] + " -> "
Jon Hall43060f62020-06-23 13:13:33 -0700591 pingResult = hostPair[ 0 ].ping( ipDst, interface=srcIface, wait=int( wait ) )
Siddesh559d7412021-10-01 06:01:55 +0000592 resultList.append({ "src": hostPair[0].shortName, "vlan": str(VLAN), "dst": hostPair[1].shortName, "result": pingResult})
Siddeshde1d1692021-09-15 18:12:57 +0000593 if pingResult:
594 pingResponse += hostPair[ 1 ].options[ 'shortName' ]
595 if VLAN:
596 pingResponse += "." + str( VLAN )
597 else:
598 pingResponse += "X"
599 # One of the host to host pair is unreachable
600 isReachable = main.FALSE
601 failedPings += 1
602 pingResponse += "\n"
You Wang84f981d2018-01-12 16:11:50 -0800603 main.log.info( pingResponse + "Failed pings: " + str( failedPings ) )
Siddesh559d7412021-10-01 06:01:55 +0000604 if returnResult:
605 return resultList
606 else:
607 return isReachable
You Wang84f981d2018-01-12 16:11:50 -0800608 except Exception:
609 main.log.exception( self.name + ": Uncaught exception!" )
610 main.cleanAndExit()
611
Jon Hall43060f62020-06-23 13:13:33 -0700612 def pingallHostsUnidirectional( self, srcList, dstList, ipv6=False, wait=1, acceptableFailed=0, useScapy=False ):
613 """
614 Verify ping from each host in srcList to each host in dstList
615
616 acceptableFailed: max number of acceptable failed pings
617
618 Returns main.TRUE if all src hosts can reach all dst hosts
619 Returns main.FALSE if one or more of src hosts cannot reach one or more of dst hosts
620 """
621 try:
622 main.log.info( "Verifying ping from each src host to each dst host" )
623
624 srcComponentList = []
625 for hostName in srcList:
626 hostComponent = self.hosts[ hostName ]
627 if hostComponent:
628 main.log.debug( repr( hostComponent ) )
629 srcComponentList.append( hostComponent )
630 dstComponentList = []
631 for hostName in dstList:
632 hostComponent = self.hosts[ hostName ]
633 if hostComponent:
634 main.log.debug( repr( hostComponent ) )
635 dstComponentList.append( hostComponent )
636
637 isReachable = main.TRUE
638 wait = int( wait )
639 cmd = " ping" + ("6" if ipv6 else "") + " -c 1 -i 1 -W " + str( wait ) + " "
640 pingResponse = "Ping output:\n"
641 failedPingsTotal = 0
642 for srcHost in srcComponentList:
643 pingResponse += str( str( srcHost.shortName ) + " -> " )
644 for dstHost in dstComponentList:
645 failedPings = 0
Jon Hall06fd0df2021-01-25 15:50:06 -0800646 dstIP = dstHost.ip_address
Jon Hall43060f62020-06-23 13:13:33 -0700647 assert dstIP, "Not able to get IP address of host {}".format( dstHost )
648 for iface in srcHost.interfaces:
649 # FIXME This only works if one iface name is configured
650 # NOTE: We can use an IP with -I instead of an interface name as well
651 name = iface.get( 'name' )
652 if name:
653 cmd += " -I %s " % name
654
655 if useScapy:
656 while failedPings <= acceptableFailed:
657 main.log.debug( "Pinging from " + str( srcHost.shortName ) + " to " + str( dstHost.shortName ) )
658 # Use scapy to send and recieve packets
659 dstHost.startFilter()
Jon Hall627b1572020-12-01 12:01:15 -0800660 srcHost.buildEther( src=srcHost.interfaces[0].get( 'mac'), dst=dstHost.interfaces[0].get( 'mac') )
Jon Hall43060f62020-06-23 13:13:33 -0700661 srcHost.sendPacket()
662 output = dstHost.checkFilter()
663 main.log.debug( output )
664 if output:
Jon Hall9b0de1f2020-08-24 15:38:04 -0700665 # TODO: parse output?
Jon Hall43060f62020-06-23 13:13:33 -0700666 packets = dstHost.readPackets()
667 for packet in packets.splitlines():
668 main.log.debug( packet )
669 pingResponse += " " + str( dstHost.shortName )
670 break
671 else:
672 kill = dstHost.killFilter()
673 main.log.debug( kill )
674 dstHost.handle.sendline( "" )
675 dstHost.handle.expect( dstHost.scapyPrompt )
676 main.log.debug( dstHost.handle.before )
677 failedPings += 1
678 time.sleep(1)
679 if failedPings > acceptableFailed:
680 # One of the host to host pair is unreachable
681 pingResponse += " X"
682 isReachable = main.FALSE
683 failedPingsTotal += 1
684
685 else:
686 pingCmd = cmd + str( dstIP )
687 while failedPings <= acceptableFailed:
688 main.log.debug( "Pinging from " + str( srcHost.shortName ) + " to " + str( dstHost.shortName ) )
689 self.handle.sendline( pingCmd )
690 self.handle.expect( self.prompt, timeout=wait + 5 )
691 response = self.handle.before
692 if re.search( ',\s0\%\spacket\sloss', response ):
693 pingResponse += " " + str( dstHost.shortName )
694 break
695 else:
696 failedPings += 1
697 time.sleep(1)
698 if failedPings > acceptableFailed:
699 # One of the host to host pair is unreachable
700 pingResponse += " X"
701 isReachable = main.FALSE
702 failedPingsTotal += 1
703 pingResponse += "\n"
704 main.log.info( pingResponse + "Failed pings: " + str( failedPingsTotal ) )
705 return isReachable
706 except AssertionError:
707 main.log.exception( "" )
708 return main.FALSE
709 except pexpect.TIMEOUT:
710 main.log.exception( self.name + ": TIMEOUT exception" )
711 response = self.handle.before
712 # NOTE: Send ctrl-c to make sure command is stopped
713 self.exitFromCmd( [ "Interrupt", self.prompt ] )
714 response += self.handle.before + self.handle.after
715 self.handle.sendline( "" )
716 self.handle.expect( self.prompt )
717 response += self.handle.before + self.handle.after
718 main.log.debug( response )
719 return main.FALSE
720 except pexpect.EOF:
721 main.log.error( self.name + ": EOF exception found" )
722 main.log.error( self.name + ": " + self.handle.before )
723 main.cleanAndExit()
724 except Exception:
725 main.log.exception( self.name + ": Uncaught exception!" )
726 main.cleanAndExit()
727
You Wang84f981d2018-01-12 16:11:50 -0800728 def iperftcp( self, host1="h1", host2="h2", timeout=6 ):
729 '''
730 Creates an iperf TCP test between two hosts. Returns main.TRUE if test results
731 are valid.
732 Optional:
733 timeout: The defualt timeout is 6 sec to allow enough time for a successful test to complete,
734 and short enough to stop an unsuccessful test from quiting and cleaning up mininet.
735 '''
736 main.log.info( self.name + ": Simple iperf TCP test between two hosts" )
737 # TODO: complete this function
738 return main.TRUE
You Wang4cc61912018-08-28 10:10:58 -0700739
740 def update( self ):
741 return main.TRUE
742
743 def verifyHostIp( self, hostList=[], prefix="", update=False ):
744 """
745 Description:
746 Verify that all hosts have IP address assigned to them
747 Optional:
748 hostList: If specified, verifications only happen to the hosts
749 in hostList
750 prefix: at least one of the ip address assigned to the host
751 needs to have the specified prefix
752 Returns:
753 main.TRUE if all hosts have specific IP address assigned;
754 main.FALSE otherwise
755 """
756 try:
You Wang4cc61912018-08-28 10:10:58 -0700757 if not hostList:
You Wangb1665b52019-02-01 15:49:48 -0800758 hostList = self.hosts.keys()
759 for hostName, hostComponent in self.hosts.items():
You Wang4cc61912018-08-28 10:10:58 -0700760 if hostName not in hostList:
761 continue
762 ipList = []
763 ipa = hostComponent.ip()
764 ipv4Pattern = r'inet ((?:[0-9]{1,3}\.){3}[0-9]{1,3})/'
765 ipList += re.findall( ipv4Pattern, ipa )
766 # It's tricky to make regex for IPv6 addresses and this one is simplified
767 ipv6Pattern = r'inet6 ((?:[0-9a-fA-F]{1,4})?(?:[:0-9a-fA-F]{1,4}){1,7}(?:::)?(?:[:0-9a-fA-F]{1,4}){1,7})/'
768 ipList += re.findall( ipv6Pattern, ipa )
769 main.log.debug( self.name + ": IP list on host " + str( hostName ) + ": " + str( ipList ) )
770 if not ipList:
771 main.log.warn( self.name + ": Failed to discover any IP addresses on host " + str( hostName ) )
772 else:
773 if not any( ip.startswith( str( prefix ) ) for ip in ipList ):
774 main.log.warn( self.name + ": None of the IPs on host " + str( hostName ) + " has prefix " + str( prefix ) )
775 else:
776 main.log.debug( self.name + ": Found matching IP on host " + str( hostName ) )
777 hostList.remove( hostName )
778 return main.FALSE if hostList else main.TRUE
779 except KeyError:
You Wangb1665b52019-02-01 15:49:48 -0800780 main.log.exception( self.name + ": host data not as expected: " + self.hosts.keys() )
You Wang4cc61912018-08-28 10:10:58 -0700781 return None
782 except pexpect.EOF:
783 main.log.error( self.name + ": EOF exception found" )
784 main.log.error( self.name + ": " + self.handle.before )
785 main.cleanAndExit()
786 except Exception:
787 main.log.exception( self.name + ": Uncaught exception" )
788 return None
789
790 def addRoute( self, host, dstIP, interface, ipv6=False ):
791 """
792 Add a route to host
793 Ex: h1 route add -host 224.2.0.1 h1-eth0
794 """
795 try:
796 if ipv6:
797 cmd = "sudo route -A inet6 add "
798 else:
799 cmd = "sudo route add -host "
800 cmd += str( dstIP ) + " " + str( interface )
801 response = self.runCmdOnHost( host, cmd )
802 main.log.debug( "response = " + response )
803 return main.TRUE
804 except pexpect.TIMEOUT:
805 main.log.error( self.name + ": TIMEOUT exception found" )
806 main.log.error( self.name + ": " + self.handle.before )
807 main.cleanAndExit()
808 except pexpect.EOF:
809 main.log.error( self.name + ": EOF exception found" )
810 main.log.error( self.name + ": " + self.handle.before )
811 return main.FALSE
812 except Exception:
813 main.log.exception( self.name + ": Uncaught exception!" )
814 main.cleanAndExit()
You Wang0fc21702018-11-02 17:49:18 -0700815
Jon Hall627b1572020-12-01 12:01:15 -0800816 def getIPAddress( self, host, iface=None, proto='IPV4' ):
You Wang0fc21702018-11-02 17:49:18 -0700817 """
818 Returns IP address of the host
819 """
Jon Hall32c90f32021-06-24 16:32:44 -0700820 hostComponent = self.hosts[ host ]
821 if hostComponent:
822 return hostComponent.getIPAddress( iface=iface, proto=proto )
You Wang0fc21702018-11-02 17:49:18 -0700823 else:
Jon Hall32c90f32021-06-24 16:32:44 -0700824 main.log.warn( self.name + ": Could not find host with short name '%s'" % host )
You Wang0fc21702018-11-02 17:49:18 -0700825 return None
You Wangb1665b52019-02-01 15:49:48 -0800826
827 def getLinkRandom( self, timeout=60, nonCut=True, excludeNodes=[], skipLinks=[] ):
828 """
829 Randomly get a link from network topology.
830 If nonCut is True, it gets a list of non-cut links (the deletion
831 of a non-cut link will not increase the number of connected
832 component of a graph) and randomly returns one of them, otherwise
833 it just randomly returns one link from all current links.
834 excludeNodes will be passed to getLinks and getGraphDict method.
835 Any link that has either end included in skipLinks will be excluded.
836 Returns the link as a list, e.g. [ 's1', 's2' ].
837 """
You Wangb1665b52019-02-01 15:49:48 -0800838 candidateLinks = []
839 try:
840 if not nonCut:
841 links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
842 assert len( links ) != 0
843 for link in links:
844 # Exclude host-switch link
845 if link[ 'node1' ].startswith( 'h' ) or link[ 'node2' ].startswith( 'h' ):
846 continue
847 candidateLinks.append( [ link[ 'node1' ], link[ 'node2' ] ] )
848 else:
849 graphDict = self.getGraphDict( timeout=timeout, useId=False,
850 excludeNodes=excludeNodes )
851 if graphDict is None:
852 return None
853 self.graph.update( graphDict )
854 candidateLinks = self.graph.getNonCutEdges()
855 candidateLinks = [ link for link in candidateLinks
856 if link[0] not in skipLinks and link[1] not in skipLinks ]
857 if candidateLinks is None:
858 return None
859 elif len( candidateLinks ) == 0:
860 main.log.info( self.name + ": No candidate link for deletion" )
861 return None
862 else:
863 link = random.sample( candidateLinks, 1 )
864 return link[ 0 ]
865 except KeyError:
866 main.log.exception( self.name + ": KeyError exception found" )
867 return None
868 except AssertionError:
869 main.log.exception( self.name + ": AssertionError exception found" )
870 return None
871 except Exception:
872 main.log.exception( self.name + ": Uncaught exception" )
873 return None
874
875 def getSwitchRandom( self, timeout=60, nonCut=True, excludeNodes=[], skipSwitches=[] ):
876 """
877 Randomly get a switch from network topology.
878 If nonCut is True, it gets a list of non-cut switches (the deletion
879 of a non-cut switch will not increase the number of connected
880 components of a graph) and randomly returns one of them, otherwise
881 it just randomly returns one switch from all current switches in
882 Mininet.
883 excludeNodes will be pased to getSwitches and getGraphDict method.
884 Switches specified in skipSwitches will be excluded.
885 Returns the name of the chosen switch.
886 """
You Wangb1665b52019-02-01 15:49:48 -0800887 candidateSwitches = []
888 try:
889 if not nonCut:
890 switches = self.getSwitches( timeout=timeout, excludeNodes=excludeNodes )
891 assert len( switches ) != 0
892 for switchName in switches.keys():
893 candidateSwitches.append( switchName )
894 else:
895 graphDict = self.getGraphDict( timeout=timeout, useId=False,
896 excludeNodes=excludeNodes )
897 if graphDict is None:
898 return None
899 self.graph.update( graphDict )
900 candidateSwitches = self.graph.getNonCutVertices()
901 candidateSwitches = [ switch for switch in candidateSwitches if switch not in skipSwitches ]
902 if candidateSwitches is None:
903 return None
904 elif len( candidateSwitches ) == 0:
905 main.log.info( self.name + ": No candidate switch for deletion" )
906 return None
907 else:
908 switch = random.sample( candidateSwitches, 1 )
909 return switch[ 0 ]
910 except KeyError:
911 main.log.exception( self.name + ": KeyError exception found" )
912 return None
913 except AssertionError:
914 main.log.exception( self.name + ": AssertionError exception found" )
915 return None
916 except Exception:
917 main.log.exception( self.name + ": Uncaught exception" )
918 return None
919
920 def getGraphDict( self, timeout=60, useId=True, includeHost=False,
921 excludeNodes=[] ):
922 """
923 Return a dictionary which describes the latest network topology data as a
924 graph.
925 An example of the dictionary:
926 { vertex1: { 'edges': ..., 'name': ..., 'protocol': ... },
927 vertex2: { 'edges': ..., 'name': ..., 'protocol': ... } }
928 Each vertex should at least have an 'edges' attribute which describes the
929 adjacency information. The value of 'edges' attribute is also represented by
930 a dictionary, which maps each edge (identified by the neighbor vertex) to a
931 list of attributes.
932 An example of the edges dictionary:
933 'edges': { vertex2: { 'port': ..., 'weight': ... },
934 vertex3: { 'port': ..., 'weight': ... } }
935 If useId == True, dpid/mac will be used instead of names to identify
936 vertices, which is helpful when e.g. comparing network topology with ONOS
937 topology.
938 If includeHost == True, all hosts (and host-switch links) will be included
939 in topology data.
940 excludeNodes will be passed to getSwitches and getLinks methods to exclude
941 unexpected switches and links.
942 """
943 # TODO: support excludeNodes
944 graphDict = {}
945 try:
946 links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
947 portDict = {}
948 switches = self.getSwitches( excludeNodes=excludeNodes )
949 if includeHost:
950 hosts = self.getHosts()
951 for link in links:
952 # TODO: support 'includeHost' argument
953 if link[ 'node1' ].startswith( 'h' ) or link[ 'node2' ].startswith( 'h' ):
954 continue
955 nodeName1 = link[ 'node1' ]
956 nodeName2 = link[ 'node2' ]
957 if not self.switches[ nodeName1 ].isup or not self.switches[ nodeName2 ].isup:
958 continue
959 port1 = link[ 'port1' ]
960 port2 = link[ 'port2' ]
961 # Loop for two nodes
962 for i in range( 2 ):
963 portIndex = port1
964 if useId:
965 node1 = 'of:' + str( switches[ nodeName1 ][ 'dpid' ] )
966 node2 = 'of:' + str( switches[ nodeName2 ][ 'dpid' ] )
967 else:
968 node1 = nodeName1
969 node2 = nodeName2
970 if node1 not in graphDict.keys():
971 if useId:
972 graphDict[ node1 ] = { 'edges': {},
973 'dpid': switches[ nodeName1 ][ 'dpid' ],
974 'name': nodeName1,
975 'ports': switches[ nodeName1 ][ 'ports' ],
976 'swClass': switches[ nodeName1 ][ 'swClass' ],
977 'pid': switches[ nodeName1 ][ 'pid' ],
978 'options': switches[ nodeName1 ][ 'options' ] }
979 else:
980 graphDict[ node1 ] = { 'edges': {} }
981 else:
982 # Assert node2 is not connected to any current links of node1
983 # assert node2 not in graphDict[ node1 ][ 'edges' ].keys()
984 pass
985 for port in switches[ nodeName1 ][ 'ports' ]:
986 if port[ 'of_port' ] == str( portIndex ):
987 # Use -1 as index for disabled port
988 if port[ 'enabled' ]:
989 graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': portIndex }
990 else:
991 graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': -1 }
992 # Swap two nodes/ports
993 nodeName1, nodeName2 = nodeName2, nodeName1
994 port1, port2 = port2, port1
995 # Remove links with disabled ports
996 linksToRemove = []
997 for node, edges in graphDict.items():
998 for neighbor, port in edges[ 'edges' ].items():
999 if port[ 'port' ] == -1:
1000 linksToRemove.append( ( node, neighbor ) )
1001 for node1, node2 in linksToRemove:
1002 for i in range( 2 ):
1003 if graphDict.get( node1 )[ 'edges' ].get( node2 ):
1004 graphDict[ node1 ][ 'edges' ].pop( node2 )
1005 node1, node2 = node2, node1
1006 return graphDict
1007 except KeyError:
1008 main.log.exception( self.name + ": KeyError exception found" )
1009 return None
1010 except AssertionError:
1011 main.log.exception( self.name + ": AssertionError exception found" )
1012 return None
1013 except pexpect.EOF:
1014 main.log.error( self.name + ": EOF exception found" )
1015 main.log.error( self.name + ": " + self.handle.before )
1016 main.cleanAndExit()
1017 except Exception:
1018 main.log.exception( self.name + ": Uncaught exception" )
1019 return None
1020
1021 def switch( self, **switchargs ):
1022 """
1023 start/stop a switch
1024 """
1025 args = utilities.parse_args( [ "SW", "OPTION" ], **switchargs )
1026 sw = args[ "SW" ] if args[ "SW" ] is not None else ""
1027 option = args[ "OPTION" ] if args[ "OPTION" ] is not None else ""
1028 try:
1029 switchComponent = self.switches[ sw ]
1030 if option == 'stop':
1031 switchComponent.stopOfAgent()
1032 elif option == 'start':
1033 switchComponent.startOfAgent()
1034 else:
1035 main.log.warn( self.name + ": Unknown switch command" )
1036 return main.FALSE
1037 return main.TRUE
1038 except KeyError:
1039 main.log.error( self.name + ": Not able to find switch [}".format( sw ) )
1040 except pexpect.TIMEOUT:
1041 main.log.error( self.name + ": TIMEOUT exception found" )
1042 main.log.error( self.name + ": " + self.handle.before )
1043 return None
1044 except pexpect.EOF:
1045 main.log.error( self.name + ": EOF exception found" )
1046 main.log.error( self.name + ": " + self.handle.before )
1047 main.cleanAndExit()
1048 except Exception:
1049 main.log.exception( self.name + ": Uncaught exception" )
1050 main.cleanAndExit()
1051
1052 def discoverHosts( self, hostList=[], wait=1000, dstIp="6.6.6.6", dstIp6="1020::3fe" ):
1053 '''
1054 Hosts in hostList will do a single ARP/ND to a non-existent address for ONOS to
1055 discover them. A host will use arping/ndisc6 to send ARP/ND depending on if it
1056 has IPv4/IPv6 addresses configured.
1057 Optional:
1058 hostList: a list of names of the hosts that need to be discovered. If not
1059 specified mininet will send ping from all the hosts
1060 wait: timeout for ARP/ND in milliseconds
1061 dstIp: destination address used by IPv4 hosts
1062 dstIp6: destination address used by IPv6 hosts
1063 Returns:
1064 main.TRUE if all packets were successfully sent. Otherwise main.FALSE
1065 '''
1066 try:
1067 hosts = self.getHosts()
1068 if not hostList:
1069 hostList = hosts.keys()
1070 discoveryResult = main.TRUE
1071 for host in hostList:
1072 flushCmd = ""
1073 cmd = ""
Jon Hall627b1572020-12-01 12:01:15 -08001074 intf = hosts[host]['interfaces'][0].get( 'name' )
1075 hostIp = self.getIPAddress( host, iface=intf ) or hosts[host]['interfaces'][0].get( 'ips', False )
1076 if hostIp:
Siddesh7fddba52021-10-21 22:02:23 +00001077 flushCmd = "sudo /sbin/ip neigh flush all"
Jon Hall43060f62020-06-23 13:13:33 -07001078 intfStr = "-i {}".format( intf ) if intf else ""
Jon Hall627b1572020-12-01 12:01:15 -08001079 srcIp = "-S {}".format( hostIp if isinstance( hostIp, types.StringType ) else hostIp[0] ) if intf else ""
Siddesh7fddba52021-10-21 22:02:23 +00001080 cmd = "sudo /usr/sbin/arping -c 1 -w {} {} {} {}".format( wait, intfStr, srcIp, dstIp )
You Wangb1665b52019-02-01 15:49:48 -08001081 main.log.debug( "Sending IPv4 arping from host {}".format( host ) )
1082 elif self.getIPAddress( host, proto='IPV6' ):
1083 flushCmd = "sudo ip -6 neigh flush all"
1084 intf = hosts[host]['interfaces'][0]['name']
1085 cmd = "ndisc6 -r 1 -w {} {} {}".format( wait, dstIp6, intf )
1086 main.log.debug( "Sending IPv6 ND from host {}".format( host ) )
1087 else:
1088 main.log.warn( "No IP addresses configured on host {}, skipping discovery".format( host ) )
1089 discoveryResult = main.FALSE
1090 if cmd:
1091 self.runCmdOnHost( host, flushCmd )
1092 self.runCmdOnHost( host, cmd )
1093 return discoveryResult
1094 except Exception:
1095 main.log.exception( self.name + ": Uncaught exception!" )
1096 main.cleanAndExit()