blob: 1cd242c8356e744b4a0698a553b39d9e3823092f [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
34from drivers.common.clidriver import CLI
You Wangb1665b52019-02-01 15:49:48 -080035from core.graph import Graph
You Wang84f981d2018-01-12 16:11:50 -080036
37class NetworkDriver( CLI ):
38
39 def __init__( self ):
40 """
41 switches: a dictionary that maps switch names to components
42 hosts: a dictionary that maps host names to components
43 """
44 self.name = None
45 self.home = None
46 self.handle = None
47 self.switches = {}
48 self.hosts = {}
You Wangb1665b52019-02-01 15:49:48 -080049 self.links = {}
You Wang84f981d2018-01-12 16:11:50 -080050 super( NetworkDriver, self ).__init__()
You Wangb1665b52019-02-01 15:49:48 -080051 self.graph = Graph()
You Wang84f981d2018-01-12 16:11:50 -080052
53 def checkOptions( self, var, defaultVar ):
54 if var is None or var == "":
55 return defaultVar
56 return var
57
58 def connect( self, **connectargs ):
59 """
60 Creates ssh handle for the SDN network "bench".
61 NOTE:
62 The ip_address would come from the topo file using the host tag, the
63 value can be an environment variable as well as a "localhost" to get
64 the ip address needed to ssh to the "bench"
65 """
66 try:
67 for key in connectargs:
68 vars( self )[ key ] = connectargs[ key ]
69 self.name = self.options[ 'name' ]
70 try:
71 if os.getenv( str( self.ip_address ) ) is not None:
72 self.ip_address = os.getenv( str( self.ip_address ) )
73 else:
74 main.log.info( self.name +
75 ": Trying to connect to " +
76 self.ip_address )
77 except KeyError:
78 main.log.info( "Invalid host name," +
79 " connecting to local host instead" )
80 self.ip_address = 'localhost'
81 except Exception as inst:
82 main.log.error( "Uncaught exception: " + str( inst ) )
83
84 self.handle = super( NetworkDriver, self ).connect(
85 user_name=self.user_name,
86 ip_address=self.ip_address,
87 port=self.port,
88 pwd=self.pwd )
89
90 if self.handle:
91 main.log.info( "Connected to network bench node" )
92 return self.handle
93 else:
94 main.log.info( "Failed to create handle" )
95 return main.FALSE
96 except pexpect.EOF:
97 main.log.error( self.name + ": EOF exception found" )
98 main.log.error( self.name + ": " + self.handle.before )
99 main.cleanAndExit()
100 except Exception:
101 main.log.exception( self.name + ": Uncaught exception!" )
102 main.cleanAndExit()
103
104 def disconnect( self ):
105 """
106 Called when test is complete to disconnect the handle.
107 """
108 response = main.TRUE
109 try:
110 if self.handle:
111 self.handle.sendline( "exit" )
112 self.handle.expect( "closed" )
113 except pexpect.EOF:
114 main.log.error( self.name + ": EOF exception found" )
115 main.log.error( self.name + ": " + self.handle.before )
116 except Exception:
117 main.log.exception( self.name + ": Connection failed to the host" )
118 response = main.FALSE
119 return response
120
121 def connectToNet( self ):
122 """
123 Connect to an existing physical network by getting information
124 of all switch and host components created
125 """
126 try:
127 for key, value in main.componentDictionary.items():
128 if hasattr( main, key ):
Pier6a0c4de2018-03-18 16:01:30 -0700129 if value[ 'type' ] in [ 'MininetSwitchDriver', 'OFDPASwitchDriver' ]:
You Wang4cc61912018-08-28 10:10:58 -0700130 component = getattr( main, key )
131 shortName = component.options[ 'shortName' ]
132 localName = self.name + "-" + shortName
133 self.copyComponent( key, localName )
134 self.switches[ shortName ] = getattr( main, localName )
Pier6a0c4de2018-03-18 16:01:30 -0700135 elif value[ 'type' ] in [ 'MininetHostDriver', 'HostDriver' ]:
You Wang4cc61912018-08-28 10:10:58 -0700136 component = getattr( main, key )
137 shortName = component.options[ 'shortName' ]
138 localName = self.name + "-" + shortName
139 self.copyComponent( key, localName )
140 self.hosts[ shortName ] = getattr( main, localName )
141 main.log.debug( self.name + ": found switches: {}".format( self.switches ) )
142 main.log.debug( self.name + ": found hosts: {}".format( self.hosts ) )
You Wang84f981d2018-01-12 16:11:50 -0800143 return main.TRUE
144 except Exception:
145 main.log.error( self.name + ": failed to connect to network" )
146 return main.FALSE
147
You Wang4cc61912018-08-28 10:10:58 -0700148 def disconnectFromNet( self ):
You Wang84f981d2018-01-12 16:11:50 -0800149 """
You Wang4cc61912018-08-28 10:10:58 -0700150 Disconnect from the physical network connected
You Wang84f981d2018-01-12 16:11:50 -0800151 """
You Wang84f981d2018-01-12 16:11:50 -0800152 try:
You Wang4cc61912018-08-28 10:10:58 -0700153 for key, value in main.componentDictionary.items():
154 if hasattr( main, key ) and key.startswith( self.name + "-" ):
155 self.removeComponent( key )
156 self.switches = {}
157 self.hosts = {}
158 return main.TRUE
You Wang84f981d2018-01-12 16:11:50 -0800159 except Exception:
You Wang4cc61912018-08-28 10:10:58 -0700160 main.log.error( self.name + ": failed to disconnect from network" )
161 return main.FALSE
162
163 def copyComponent( self, name, newName ):
164 """
165 Copy the component initialized from the .topo file
166 The copied components are only supposed to be called within this driver
167 Required:
168 name: name of the component to be copied
169 newName: name of the new component
170 """
171 try:
172 main.componentDictionary[ newName ] = main.componentDictionary[ name ].copy()
173 main.componentInit( newName )
174 except Exception:
175 main.log.exception( self.name + ": Uncaught exception!" )
176 main.cleanAndExit()
177
178 def removeHostComponent( self, name ):
179 """
180 Remove host component
181 Required:
182 name: name of the component to be removed
183 """
184 try:
185 self.removeComponent( name )
186 except Exception:
187 main.log.exception( self.name + ": Uncaught exception!" )
188 main.cleanAndExit()
189
190 def removeComponent( self, name ):
191 """
192 Remove host/switch component
193 Required:
194 name: name of the component to be removed
195 """
196 try:
197 component = getattr( main, name )
198 except AttributeError:
199 main.log.error( "Component " + name + " does not exist." )
200 return main.FALSE
201 try:
202 # Disconnect from component
203 component.disconnect()
204 # Delete component
205 delattr( main, name )
206 # Delete component from ComponentDictionary
207 del( main.componentDictionary[ name ] )
208 return main.TRUE
209 except Exception:
210 main.log.exception( self.name + ": Uncaught exception!" )
211 main.cleanAndExit()
212
You Wang0fc21702018-11-02 17:49:18 -0700213 def createHostComponent( self, name ):
You Wang4cc61912018-08-28 10:10:58 -0700214 """
You Wang0fc21702018-11-02 17:49:18 -0700215 Creates host component with the same parameters as the one copied to local.
You Wang4cc61912018-08-28 10:10:58 -0700216 Arguments:
217 name - The string of the name of this component. The new component
218 will be assigned to main.<name> .
219 In addition, main.<name>.name = str( name )
220 """
221 try:
222 # look to see if this component already exists
223 getattr( main, name )
224 except AttributeError:
225 # namespace is clear, creating component
226 localName = self.name + "-" + name
227 main.componentDictionary[ name ] = main.componentDictionary[ localName ].copy()
228 main.componentInit( name )
229 except Exception:
230 main.log.exception( self.name + ": Uncaught exception!" )
231 main.cleanAndExit()
232 else:
233 # namespace is not clear!
234 main.log.error( name + " component already exists!" )
235 main.cleanAndExit()
236
237 def connectInbandHosts( self ):
238 """
239 Connect to hosts using data plane IPs specified
240 """
241 result = main.TRUE
242 try:
243 for hostName, hostComponent in self.hosts.items():
244 if hostComponent.options[ 'inband' ] == 'True':
245 main.log.info( self.name + ": connecting inband host " + hostName )
246 result = hostComponent.connectInband() and result
247 return result
248 except Exception:
249 main.log.error( self.name + ": failed to connect to inband hosts" )
250 return main.FALSE
251
252 def disconnectInbandHosts( self ):
253 """
254 Terminate the connections to hosts using data plane IPs
255 """
256 result = main.TRUE
257 try:
258 for hostName, hostComponent in self.hosts.items():
259 if hostComponent.options[ 'inband' ] == 'True':
260 main.log.info( self.name + ": disconnecting inband host " + hostName )
261 result = hostComponent.disconnectInband() and result
262 return result
263 except Exception:
264 main.log.error( self.name + ": failed to disconnect inband hosts" )
265 return main.FALSE
266
You Wangb1665b52019-02-01 15:49:48 -0800267 def getSwitches( self, timeout=60, excludeNodes=[], includeStopped=False ):
You Wang4cc61912018-08-28 10:10:58 -0700268 """
You Wangb1665b52019-02-01 15:49:48 -0800269 Return a dictionary which maps short names to switch data
270 If includeStopped is True, stopped switches will also be included
You Wang4cc61912018-08-28 10:10:58 -0700271 """
You Wangb1665b52019-02-01 15:49:48 -0800272 switches = {}
273 for switchName, switchComponent in self.switches.items():
274 if switchName in excludeNodes:
275 continue
276 if not includeStopped and not switchComponent.isup:
277 continue
278 dpid = switchComponent.dpid.replace( '0x', '' ).zfill( 16 )
279 ports = switchComponent.ports
280 swClass = 'Unknown'
281 pid = None
282 options = None
283 switches[ switchName ] = { "dpid": dpid,
284 "ports": ports,
285 "swClass": swClass,
286 "pid": pid,
287 "options": options }
288 return switches
You Wang4cc61912018-08-28 10:10:58 -0700289
You Wangb1665b52019-02-01 15:49:48 -0800290 def getHosts( self, hostClass=None ):
You Wang4cc61912018-08-28 10:10:58 -0700291 """
You Wangb1665b52019-02-01 15:49:48 -0800292 Return a dictionary which maps short names to host data
You Wang4cc61912018-08-28 10:10:58 -0700293 """
You Wangb1665b52019-02-01 15:49:48 -0800294 hosts = {}
295 for hostName, hostComponent in self.hosts.items():
296 interfaces = hostComponent.interfaces
297 hosts[ hostName ] = { "interfaces": interfaces }
298 return hosts
299
300 def updateLinks( self, timeout=60, excludeNodes=[] ):
301 """
302 Update self.links by getting up-to-date port information from
303 switches
304 """
305 # TODO: also inlcude switch-to-host links
306 self.links = {}
307 for node1 in self.switches.keys():
308 if node1 in excludeNodes:
309 continue
310 self.links[ node1 ] = {}
311 self.switches[ node1 ].updatePorts()
312 for port in self.switches[ node1 ].ports:
313 if not port[ 'enabled' ]:
314 continue
315 node2 = getattr( main, port[ 'node2' ] ).shortName
316 if node2 in excludeNodes:
317 continue
318 port1 = port[ 'of_port' ]
319 port2 = port[ 'port2' ]
320 if not self.links[ node1 ].get( node2 ):
321 self.links[ node1 ][ node2 ] = {}
322 # Check if this link already exists
323 if self.links.get( node2 ):
324 if self.links[ node2 ].get( node1 ):
325 if self.links[ node2 ].get( node1 ).get( port2 ):
326 assert self.links[ node2 ][ node1 ][ port2 ] == port1
327 continue
328 self.links[ node1 ][ node2 ][ port1 ] = port2
329
330 def getLinks( self, timeout=60, excludeNodes=[] ):
331 """
332 Return a list of links specify both node names and port numbers
333 """
334 self.updateLinks( timeout=timeout, excludeNodes=excludeNodes )
335 links = []
336 for node1, nodeLinks in self.links.items():
337 for node2, ports in nodeLinks.items():
338 for port1, port2 in ports.items():
339 links.append( { 'node1': node1, 'node2': node2,
340 'port1': port1, 'port2': port2 } )
341 return links
You Wang84f981d2018-01-12 16:11:50 -0800342
343 def getMacAddress( self, host ):
344 """
345 Return MAC address of a host
346 """
347 import re
348 try:
You Wangb1665b52019-02-01 15:49:48 -0800349 hostComponent = self.hosts[ host ]
You Wang84f981d2018-01-12 16:11:50 -0800350 response = hostComponent.ifconfig()
351 pattern = r'HWaddr\s([0-9A-F]{2}[:-]){5}([0-9A-F]{2})'
352 macAddressSearch = re.search( pattern, response, re.I )
353 macAddress = macAddressSearch.group().split( " " )[ 1 ]
354 main.log.info( self.name + ": Mac-Address of Host " + host + " is " + macAddress )
355 return macAddress
356 except Exception:
357 main.log.error( self.name + ": failed to get host MAC address" )
358
You Wang4cc61912018-08-28 10:10:58 -0700359 def runCmdOnHost( self, hostName, cmd ):
You Wang84f981d2018-01-12 16:11:50 -0800360 """
You Wang4cc61912018-08-28 10:10:58 -0700361 Run shell command on specified host and return output
362 Required:
363 hostName: name of the host e.g. "h1"
364 cmd: command to run on the host
You Wang84f981d2018-01-12 16:11:50 -0800365 """
You Wangb1665b52019-02-01 15:49:48 -0800366 hostComponent = self.hosts[ hostName ]
You Wang4cc61912018-08-28 10:10:58 -0700367 if hostComponent:
368 return hostComponent.command( cmd )
You Wang84f981d2018-01-12 16:11:50 -0800369 return None
370
371 def assignSwController( self, sw, ip, port="6653", ptcp="" ):
372 """
373 Description:
374 Assign switches to the controllers
375 Required:
376 sw - Short name of the switch specified in the .topo file, e.g. "s1".
377 It can also be a list of switch names.
378 ip - Ip addresses of controllers. This can be a list or a string.
379 Optional:
380 port - ONOS use port 6653, if no list of ports is passed, then
381 the all the controller will use 6653 as their port number
382 ptcp - ptcp number, This can be a string or a list that has
383 the same length as switch. This is optional and not required
384 when using ovs switches.
385 NOTE: If switches and ptcp are given in a list type they should have the
386 same length and should be in the same order, Eg. sw=[ 's1' ... n ]
387 ptcp=[ '6637' ... n ], s1 has ptcp number 6637 and so on.
388
389 Return:
390 Returns main.TRUE if switches are correctly assigned to controllers,
391 otherwise it will return main.FALSE or an appropriate exception(s)
392 """
393 switchList = []
394 ptcpList = None
395 try:
396 if isinstance( sw, types.StringType ):
397 switchList.append( sw )
398 if ptcp:
399 if isinstance( ptcp, types.StringType ):
400 ptcpList = [ ptcp ]
401 elif isinstance( ptcp, types.ListType ):
402 main.log.error( self.name + ": Only one switch is " +
403 "being set and multiple PTCP is " +
404 "being passed " )
405 return main.FALSE
406 else:
407 main.log.error( self.name + ": Invalid PTCP" )
408 return main.FALSE
409
410 elif isinstance( sw, types.ListType ):
411 switchList = sw
412 if ptcp:
413 if isinstance( ptcp, types.ListType ):
414 if len( ptcp ) != len( sw ):
415 main.log.error( self.name + ": PTCP length = " +
416 str( len( ptcp ) ) +
417 " is not the same as switch" +
418 " length = " +
419 str( len( sw ) ) )
420 return main.FALSE
421 else:
422 ptcpList = ptcp
423 else:
424 main.log.error( self.name + ": Invalid PTCP" )
425 return main.FALSE
426 else:
427 main.log.error( self.name + ": Invalid switch type " )
428 return main.FALSE
429
430 assignResult = main.TRUE
431 index = 0
432 for switch in switchList:
433 assigned = False
You Wang4cc61912018-08-28 10:10:58 -0700434 switchComponent = self.switches[ switch ]
You Wang84f981d2018-01-12 16:11:50 -0800435 if switchComponent:
436 ptcp = ptcpList[ index ] if ptcpList else ""
437 assignResult = assignResult and switchComponent.assignSwController( ip=ip, port=port, ptcp=ptcp )
438 assigned = True
439 if not assigned:
440 main.log.error( self.name + ": Not able to find switch " + switch )
441 assignResult = main.FALSE
442 index += 1
443 return assignResult
444
445 except Exception:
446 main.log.exception( self.name + ": Uncaught exception!" )
447 main.cleanAndExit()
448
449 def pingall( self, protocol="IPv4", timeout=300, shortCircuit=False, acceptableFailed=0 ):
450 """
451 Description:
452 Verifies the reachability of the hosts using ping command.
453 Optional:
454 protocol - use ping6 command if specified as "IPv6"
455 timeout( seconds ) - How long to wait before breaking the pingall
456 shortCircuit - Break the pingall based on the number of failed hosts ping
457 acceptableFailed - Set the number of acceptable failed pings for the
458 function to still return main.TRUE
459 Returns:
460 main.TRUE if pingall completes with no pings dropped
461 otherwise main.FALSE
462 """
463 import time
464 import itertools
465 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
498 def pingallHosts( self, hostList, wait=1 ):
499 """
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"""
510 import time
511 import itertools
512 hostComponentList = []
513 for hostName in hostList:
You Wangb1665b52019-02-01 15:49:48 -0800514 hostComponent = self.hosts[ hostName ]
You Wang84f981d2018-01-12 16:11:50 -0800515 if hostComponent:
516 hostComponentList.append( hostComponent )
517 try:
518 main.log.info( "Testing reachability between specified hosts" )
519 isReachable = main.TRUE
520 pingResponse = "IPv4 ping across specified hosts\n"
521 failedPings = 0
522 hostPairs = itertools.permutations( list( hostComponentList ), 2 )
523 for hostPair in list( hostPairs ):
524 pingResponse += hostPair[ 0 ].options[ 'shortName' ] + " -> "
525 ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ]
526 pingResult = hostPair[ 0 ].ping( ipDst, wait=int( wait ) )
527 if pingResult:
528 pingResponse += hostPair[ 1 ].options[ 'shortName' ]
529 else:
530 pingResponse += "X"
531 # One of the host to host pair is unreachable
532 isReachable = main.FALSE
533 failedPings += 1
534 pingResponse += "\n"
535 main.log.info( pingResponse + "Failed pings: " + str( failedPings ) )
536 return isReachable
537 except Exception:
538 main.log.exception( self.name + ": Uncaught exception!" )
539 main.cleanAndExit()
540
541 def iperftcp( self, host1="h1", host2="h2", timeout=6 ):
542 '''
543 Creates an iperf TCP test between two hosts. Returns main.TRUE if test results
544 are valid.
545 Optional:
546 timeout: The defualt timeout is 6 sec to allow enough time for a successful test to complete,
547 and short enough to stop an unsuccessful test from quiting and cleaning up mininet.
548 '''
549 main.log.info( self.name + ": Simple iperf TCP test between two hosts" )
550 # TODO: complete this function
551 return main.TRUE
You Wang4cc61912018-08-28 10:10:58 -0700552
553 def update( self ):
554 return main.TRUE
555
556 def verifyHostIp( self, hostList=[], prefix="", update=False ):
557 """
558 Description:
559 Verify that all hosts have IP address assigned to them
560 Optional:
561 hostList: If specified, verifications only happen to the hosts
562 in hostList
563 prefix: at least one of the ip address assigned to the host
564 needs to have the specified prefix
565 Returns:
566 main.TRUE if all hosts have specific IP address assigned;
567 main.FALSE otherwise
568 """
569 try:
You Wang4cc61912018-08-28 10:10:58 -0700570 if not hostList:
You Wangb1665b52019-02-01 15:49:48 -0800571 hostList = self.hosts.keys()
572 for hostName, hostComponent in self.hosts.items():
You Wang4cc61912018-08-28 10:10:58 -0700573 if hostName not in hostList:
574 continue
575 ipList = []
576 ipa = hostComponent.ip()
577 ipv4Pattern = r'inet ((?:[0-9]{1,3}\.){3}[0-9]{1,3})/'
578 ipList += re.findall( ipv4Pattern, ipa )
579 # It's tricky to make regex for IPv6 addresses and this one is simplified
580 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})/'
581 ipList += re.findall( ipv6Pattern, ipa )
582 main.log.debug( self.name + ": IP list on host " + str( hostName ) + ": " + str( ipList ) )
583 if not ipList:
584 main.log.warn( self.name + ": Failed to discover any IP addresses on host " + str( hostName ) )
585 else:
586 if not any( ip.startswith( str( prefix ) ) for ip in ipList ):
587 main.log.warn( self.name + ": None of the IPs on host " + str( hostName ) + " has prefix " + str( prefix ) )
588 else:
589 main.log.debug( self.name + ": Found matching IP on host " + str( hostName ) )
590 hostList.remove( hostName )
591 return main.FALSE if hostList else main.TRUE
592 except KeyError:
You Wangb1665b52019-02-01 15:49:48 -0800593 main.log.exception( self.name + ": host data not as expected: " + self.hosts.keys() )
You Wang4cc61912018-08-28 10:10:58 -0700594 return None
595 except pexpect.EOF:
596 main.log.error( self.name + ": EOF exception found" )
597 main.log.error( self.name + ": " + self.handle.before )
598 main.cleanAndExit()
599 except Exception:
600 main.log.exception( self.name + ": Uncaught exception" )
601 return None
602
603 def addRoute( self, host, dstIP, interface, ipv6=False ):
604 """
605 Add a route to host
606 Ex: h1 route add -host 224.2.0.1 h1-eth0
607 """
608 try:
609 if ipv6:
610 cmd = "sudo route -A inet6 add "
611 else:
612 cmd = "sudo route add -host "
613 cmd += str( dstIP ) + " " + str( interface )
614 response = self.runCmdOnHost( host, cmd )
615 main.log.debug( "response = " + response )
616 return main.TRUE
617 except pexpect.TIMEOUT:
618 main.log.error( self.name + ": TIMEOUT exception found" )
619 main.log.error( self.name + ": " + self.handle.before )
620 main.cleanAndExit()
621 except pexpect.EOF:
622 main.log.error( self.name + ": EOF exception found" )
623 main.log.error( self.name + ": " + self.handle.before )
624 return main.FALSE
625 except Exception:
626 main.log.exception( self.name + ": Uncaught exception!" )
627 main.cleanAndExit()
You Wang0fc21702018-11-02 17:49:18 -0700628
629 def getIPAddress( self, host, proto='IPV4' ):
630 """
631 Returns IP address of the host
632 """
633 response = self.runCmdOnHost( host, "ifconfig" )
634 pattern = ''
635 if proto == 'IPV4':
636 pattern = "inet\s(\d+\.\d+\.\d+\.\d+)\s\snetmask"
637 else:
638 pattern = "inet6\s([\w,:]*)/\d+\s\sprefixlen"
639 ipAddressSearch = re.search( pattern, response )
640 if not ipAddressSearch:
641 return None
642 main.log.info(
643 self.name +
644 ": IP-Address of Host " +
645 host +
646 " is " +
647 ipAddressSearch.group( 1 ) )
648 return ipAddressSearch.group( 1 )
You Wangb1665b52019-02-01 15:49:48 -0800649
650 def getLinkRandom( self, timeout=60, nonCut=True, excludeNodes=[], skipLinks=[] ):
651 """
652 Randomly get a link from network topology.
653 If nonCut is True, it gets a list of non-cut links (the deletion
654 of a non-cut link will not increase the number of connected
655 component of a graph) and randomly returns one of them, otherwise
656 it just randomly returns one link from all current links.
657 excludeNodes will be passed to getLinks and getGraphDict method.
658 Any link that has either end included in skipLinks will be excluded.
659 Returns the link as a list, e.g. [ 's1', 's2' ].
660 """
661 import random
662 candidateLinks = []
663 try:
664 if not nonCut:
665 links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
666 assert len( links ) != 0
667 for link in links:
668 # Exclude host-switch link
669 if link[ 'node1' ].startswith( 'h' ) or link[ 'node2' ].startswith( 'h' ):
670 continue
671 candidateLinks.append( [ link[ 'node1' ], link[ 'node2' ] ] )
672 else:
673 graphDict = self.getGraphDict( timeout=timeout, useId=False,
674 excludeNodes=excludeNodes )
675 if graphDict is None:
676 return None
677 self.graph.update( graphDict )
678 candidateLinks = self.graph.getNonCutEdges()
679 candidateLinks = [ link for link in candidateLinks
680 if link[0] not in skipLinks and link[1] not in skipLinks ]
681 if candidateLinks is None:
682 return None
683 elif len( candidateLinks ) == 0:
684 main.log.info( self.name + ": No candidate link for deletion" )
685 return None
686 else:
687 link = random.sample( candidateLinks, 1 )
688 return link[ 0 ]
689 except KeyError:
690 main.log.exception( self.name + ": KeyError exception found" )
691 return None
692 except AssertionError:
693 main.log.exception( self.name + ": AssertionError exception found" )
694 return None
695 except Exception:
696 main.log.exception( self.name + ": Uncaught exception" )
697 return None
698
699 def getSwitchRandom( self, timeout=60, nonCut=True, excludeNodes=[], skipSwitches=[] ):
700 """
701 Randomly get a switch from network topology.
702 If nonCut is True, it gets a list of non-cut switches (the deletion
703 of a non-cut switch will not increase the number of connected
704 components of a graph) and randomly returns one of them, otherwise
705 it just randomly returns one switch from all current switches in
706 Mininet.
707 excludeNodes will be pased to getSwitches and getGraphDict method.
708 Switches specified in skipSwitches will be excluded.
709 Returns the name of the chosen switch.
710 """
711 import random
712 candidateSwitches = []
713 try:
714 if not nonCut:
715 switches = self.getSwitches( timeout=timeout, excludeNodes=excludeNodes )
716 assert len( switches ) != 0
717 for switchName in switches.keys():
718 candidateSwitches.append( switchName )
719 else:
720 graphDict = self.getGraphDict( timeout=timeout, useId=False,
721 excludeNodes=excludeNodes )
722 if graphDict is None:
723 return None
724 self.graph.update( graphDict )
725 candidateSwitches = self.graph.getNonCutVertices()
726 candidateSwitches = [ switch for switch in candidateSwitches if switch not in skipSwitches ]
727 if candidateSwitches is None:
728 return None
729 elif len( candidateSwitches ) == 0:
730 main.log.info( self.name + ": No candidate switch for deletion" )
731 return None
732 else:
733 switch = random.sample( candidateSwitches, 1 )
734 return switch[ 0 ]
735 except KeyError:
736 main.log.exception( self.name + ": KeyError exception found" )
737 return None
738 except AssertionError:
739 main.log.exception( self.name + ": AssertionError exception found" )
740 return None
741 except Exception:
742 main.log.exception( self.name + ": Uncaught exception" )
743 return None
744
745 def getGraphDict( self, timeout=60, useId=True, includeHost=False,
746 excludeNodes=[] ):
747 """
748 Return a dictionary which describes the latest network topology data as a
749 graph.
750 An example of the dictionary:
751 { vertex1: { 'edges': ..., 'name': ..., 'protocol': ... },
752 vertex2: { 'edges': ..., 'name': ..., 'protocol': ... } }
753 Each vertex should at least have an 'edges' attribute which describes the
754 adjacency information. The value of 'edges' attribute is also represented by
755 a dictionary, which maps each edge (identified by the neighbor vertex) to a
756 list of attributes.
757 An example of the edges dictionary:
758 'edges': { vertex2: { 'port': ..., 'weight': ... },
759 vertex3: { 'port': ..., 'weight': ... } }
760 If useId == True, dpid/mac will be used instead of names to identify
761 vertices, which is helpful when e.g. comparing network topology with ONOS
762 topology.
763 If includeHost == True, all hosts (and host-switch links) will be included
764 in topology data.
765 excludeNodes will be passed to getSwitches and getLinks methods to exclude
766 unexpected switches and links.
767 """
768 # TODO: support excludeNodes
769 graphDict = {}
770 try:
771 links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
772 portDict = {}
773 switches = self.getSwitches( excludeNodes=excludeNodes )
774 if includeHost:
775 hosts = self.getHosts()
776 for link in links:
777 # TODO: support 'includeHost' argument
778 if link[ 'node1' ].startswith( 'h' ) or link[ 'node2' ].startswith( 'h' ):
779 continue
780 nodeName1 = link[ 'node1' ]
781 nodeName2 = link[ 'node2' ]
782 if not self.switches[ nodeName1 ].isup or not self.switches[ nodeName2 ].isup:
783 continue
784 port1 = link[ 'port1' ]
785 port2 = link[ 'port2' ]
786 # Loop for two nodes
787 for i in range( 2 ):
788 portIndex = port1
789 if useId:
790 node1 = 'of:' + str( switches[ nodeName1 ][ 'dpid' ] )
791 node2 = 'of:' + str( switches[ nodeName2 ][ 'dpid' ] )
792 else:
793 node1 = nodeName1
794 node2 = nodeName2
795 if node1 not in graphDict.keys():
796 if useId:
797 graphDict[ node1 ] = { 'edges': {},
798 'dpid': switches[ nodeName1 ][ 'dpid' ],
799 'name': nodeName1,
800 'ports': switches[ nodeName1 ][ 'ports' ],
801 'swClass': switches[ nodeName1 ][ 'swClass' ],
802 'pid': switches[ nodeName1 ][ 'pid' ],
803 'options': switches[ nodeName1 ][ 'options' ] }
804 else:
805 graphDict[ node1 ] = { 'edges': {} }
806 else:
807 # Assert node2 is not connected to any current links of node1
808 # assert node2 not in graphDict[ node1 ][ 'edges' ].keys()
809 pass
810 for port in switches[ nodeName1 ][ 'ports' ]:
811 if port[ 'of_port' ] == str( portIndex ):
812 # Use -1 as index for disabled port
813 if port[ 'enabled' ]:
814 graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': portIndex }
815 else:
816 graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': -1 }
817 # Swap two nodes/ports
818 nodeName1, nodeName2 = nodeName2, nodeName1
819 port1, port2 = port2, port1
820 # Remove links with disabled ports
821 linksToRemove = []
822 for node, edges in graphDict.items():
823 for neighbor, port in edges[ 'edges' ].items():
824 if port[ 'port' ] == -1:
825 linksToRemove.append( ( node, neighbor ) )
826 for node1, node2 in linksToRemove:
827 for i in range( 2 ):
828 if graphDict.get( node1 )[ 'edges' ].get( node2 ):
829 graphDict[ node1 ][ 'edges' ].pop( node2 )
830 node1, node2 = node2, node1
831 return graphDict
832 except KeyError:
833 main.log.exception( self.name + ": KeyError exception found" )
834 return None
835 except AssertionError:
836 main.log.exception( self.name + ": AssertionError exception found" )
837 return None
838 except pexpect.EOF:
839 main.log.error( self.name + ": EOF exception found" )
840 main.log.error( self.name + ": " + self.handle.before )
841 main.cleanAndExit()
842 except Exception:
843 main.log.exception( self.name + ": Uncaught exception" )
844 return None
845
846 def switch( self, **switchargs ):
847 """
848 start/stop a switch
849 """
850 args = utilities.parse_args( [ "SW", "OPTION" ], **switchargs )
851 sw = args[ "SW" ] if args[ "SW" ] is not None else ""
852 option = args[ "OPTION" ] if args[ "OPTION" ] is not None else ""
853 try:
854 switchComponent = self.switches[ sw ]
855 if option == 'stop':
856 switchComponent.stopOfAgent()
857 elif option == 'start':
858 switchComponent.startOfAgent()
859 else:
860 main.log.warn( self.name + ": Unknown switch command" )
861 return main.FALSE
862 return main.TRUE
863 except KeyError:
864 main.log.error( self.name + ": Not able to find switch [}".format( sw ) )
865 except pexpect.TIMEOUT:
866 main.log.error( self.name + ": TIMEOUT exception found" )
867 main.log.error( self.name + ": " + self.handle.before )
868 return None
869 except pexpect.EOF:
870 main.log.error( self.name + ": EOF exception found" )
871 main.log.error( self.name + ": " + self.handle.before )
872 main.cleanAndExit()
873 except Exception:
874 main.log.exception( self.name + ": Uncaught exception" )
875 main.cleanAndExit()
876
877 def discoverHosts( self, hostList=[], wait=1000, dstIp="6.6.6.6", dstIp6="1020::3fe" ):
878 '''
879 Hosts in hostList will do a single ARP/ND to a non-existent address for ONOS to
880 discover them. A host will use arping/ndisc6 to send ARP/ND depending on if it
881 has IPv4/IPv6 addresses configured.
882 Optional:
883 hostList: a list of names of the hosts that need to be discovered. If not
884 specified mininet will send ping from all the hosts
885 wait: timeout for ARP/ND in milliseconds
886 dstIp: destination address used by IPv4 hosts
887 dstIp6: destination address used by IPv6 hosts
888 Returns:
889 main.TRUE if all packets were successfully sent. Otherwise main.FALSE
890 '''
891 try:
892 hosts = self.getHosts()
893 if not hostList:
894 hostList = hosts.keys()
895 discoveryResult = main.TRUE
896 for host in hostList:
897 flushCmd = ""
898 cmd = ""
899 if self.getIPAddress( host ):
900 flushCmd = "sudo ip neigh flush all"
901 cmd = "arping -c 1 -w {} {}".format( wait, dstIp )
902 main.log.debug( "Sending IPv4 arping from host {}".format( host ) )
903 elif self.getIPAddress( host, proto='IPV6' ):
904 flushCmd = "sudo ip -6 neigh flush all"
905 intf = hosts[host]['interfaces'][0]['name']
906 cmd = "ndisc6 -r 1 -w {} {} {}".format( wait, dstIp6, intf )
907 main.log.debug( "Sending IPv6 ND from host {}".format( host ) )
908 else:
909 main.log.warn( "No IP addresses configured on host {}, skipping discovery".format( host ) )
910 discoveryResult = main.FALSE
911 if cmd:
912 self.runCmdOnHost( host, flushCmd )
913 self.runCmdOnHost( host, cmd )
914 return discoveryResult
915 except Exception:
916 main.log.exception( self.name + ": Uncaught exception!" )
917 main.cleanAndExit()