You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | """ |
| 3 | Copyright 2018 Open Networking Foundation (ONF) |
| 4 | |
| 5 | Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>, |
| 6 | the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>, |
| 7 | or 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 | |
| 23 | This driver is used to interact with a physical network that SDN controller is controlling. |
| 24 | |
| 25 | Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>, |
| 26 | the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>, |
| 27 | or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg> |
| 28 | |
| 29 | """ |
| 30 | import pexpect |
| 31 | import os |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 32 | import re |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 33 | import types |
| 34 | from drivers.common.clidriver import CLI |
| 35 | |
| 36 | class NetworkDriver( CLI ): |
| 37 | |
| 38 | def __init__( self ): |
| 39 | """ |
| 40 | switches: a dictionary that maps switch names to components |
| 41 | hosts: a dictionary that maps host names to components |
| 42 | """ |
| 43 | self.name = None |
| 44 | self.home = None |
| 45 | self.handle = None |
| 46 | self.switches = {} |
| 47 | self.hosts = {} |
| 48 | super( NetworkDriver, self ).__init__() |
| 49 | |
| 50 | def checkOptions( self, var, defaultVar ): |
| 51 | if var is None or var == "": |
| 52 | return defaultVar |
| 53 | return var |
| 54 | |
| 55 | def connect( self, **connectargs ): |
| 56 | """ |
| 57 | Creates ssh handle for the SDN network "bench". |
| 58 | NOTE: |
| 59 | The ip_address would come from the topo file using the host tag, the |
| 60 | value can be an environment variable as well as a "localhost" to get |
| 61 | the ip address needed to ssh to the "bench" |
| 62 | """ |
| 63 | try: |
| 64 | for key in connectargs: |
| 65 | vars( self )[ key ] = connectargs[ key ] |
| 66 | self.name = self.options[ 'name' ] |
| 67 | try: |
| 68 | if os.getenv( str( self.ip_address ) ) is not None: |
| 69 | self.ip_address = os.getenv( str( self.ip_address ) ) |
| 70 | else: |
| 71 | main.log.info( self.name + |
| 72 | ": Trying to connect to " + |
| 73 | self.ip_address ) |
| 74 | except KeyError: |
| 75 | main.log.info( "Invalid host name," + |
| 76 | " connecting to local host instead" ) |
| 77 | self.ip_address = 'localhost' |
| 78 | except Exception as inst: |
| 79 | main.log.error( "Uncaught exception: " + str( inst ) ) |
| 80 | |
| 81 | self.handle = super( NetworkDriver, self ).connect( |
| 82 | user_name=self.user_name, |
| 83 | ip_address=self.ip_address, |
| 84 | port=self.port, |
| 85 | pwd=self.pwd ) |
| 86 | |
| 87 | if self.handle: |
| 88 | main.log.info( "Connected to network bench node" ) |
| 89 | return self.handle |
| 90 | else: |
| 91 | main.log.info( "Failed to create handle" ) |
| 92 | return main.FALSE |
| 93 | except pexpect.EOF: |
| 94 | main.log.error( self.name + ": EOF exception found" ) |
| 95 | main.log.error( self.name + ": " + self.handle.before ) |
| 96 | main.cleanAndExit() |
| 97 | except Exception: |
| 98 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 99 | main.cleanAndExit() |
| 100 | |
| 101 | def disconnect( self ): |
| 102 | """ |
| 103 | Called when test is complete to disconnect the handle. |
| 104 | """ |
| 105 | response = main.TRUE |
| 106 | try: |
| 107 | if self.handle: |
| 108 | self.handle.sendline( "exit" ) |
| 109 | self.handle.expect( "closed" ) |
| 110 | except pexpect.EOF: |
| 111 | main.log.error( self.name + ": EOF exception found" ) |
| 112 | main.log.error( self.name + ": " + self.handle.before ) |
| 113 | except Exception: |
| 114 | main.log.exception( self.name + ": Connection failed to the host" ) |
| 115 | response = main.FALSE |
| 116 | return response |
| 117 | |
| 118 | def connectToNet( self ): |
| 119 | """ |
| 120 | Connect to an existing physical network by getting information |
| 121 | of all switch and host components created |
| 122 | """ |
| 123 | try: |
| 124 | for key, value in main.componentDictionary.items(): |
| 125 | if hasattr( main, key ): |
Pier | 6a0c4de | 2018-03-18 16:01:30 -0700 | [diff] [blame] | 126 | if value[ 'type' ] in [ 'MininetSwitchDriver', 'OFDPASwitchDriver' ]: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 127 | component = getattr( main, key ) |
| 128 | shortName = component.options[ 'shortName' ] |
| 129 | localName = self.name + "-" + shortName |
| 130 | self.copyComponent( key, localName ) |
| 131 | self.switches[ shortName ] = getattr( main, localName ) |
Pier | 6a0c4de | 2018-03-18 16:01:30 -0700 | [diff] [blame] | 132 | elif value[ 'type' ] in [ 'MininetHostDriver', 'HostDriver' ]: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 133 | component = getattr( main, key ) |
| 134 | shortName = component.options[ 'shortName' ] |
| 135 | localName = self.name + "-" + shortName |
| 136 | self.copyComponent( key, localName ) |
| 137 | self.hosts[ shortName ] = getattr( main, localName ) |
| 138 | main.log.debug( self.name + ": found switches: {}".format( self.switches ) ) |
| 139 | main.log.debug( self.name + ": found hosts: {}".format( self.hosts ) ) |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 140 | return main.TRUE |
| 141 | except Exception: |
| 142 | main.log.error( self.name + ": failed to connect to network" ) |
| 143 | return main.FALSE |
| 144 | |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 145 | def disconnectFromNet( self ): |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 146 | """ |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 147 | Disconnect from the physical network connected |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 148 | """ |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 149 | try: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 150 | for key, value in main.componentDictionary.items(): |
| 151 | if hasattr( main, key ) and key.startswith( self.name + "-" ): |
| 152 | self.removeComponent( key ) |
| 153 | self.switches = {} |
| 154 | self.hosts = {} |
| 155 | return main.TRUE |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 156 | except Exception: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 157 | main.log.error( self.name + ": failed to disconnect from network" ) |
| 158 | return main.FALSE |
| 159 | |
| 160 | def copyComponent( self, name, newName ): |
| 161 | """ |
| 162 | Copy the component initialized from the .topo file |
| 163 | The copied components are only supposed to be called within this driver |
| 164 | Required: |
| 165 | name: name of the component to be copied |
| 166 | newName: name of the new component |
| 167 | """ |
| 168 | try: |
| 169 | main.componentDictionary[ newName ] = main.componentDictionary[ name ].copy() |
| 170 | main.componentInit( newName ) |
| 171 | except Exception: |
| 172 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 173 | main.cleanAndExit() |
| 174 | |
| 175 | def removeHostComponent( self, name ): |
| 176 | """ |
| 177 | Remove host component |
| 178 | Required: |
| 179 | name: name of the component to be removed |
| 180 | """ |
| 181 | try: |
| 182 | self.removeComponent( name ) |
| 183 | except Exception: |
| 184 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 185 | main.cleanAndExit() |
| 186 | |
| 187 | def removeComponent( self, name ): |
| 188 | """ |
| 189 | Remove host/switch component |
| 190 | Required: |
| 191 | name: name of the component to be removed |
| 192 | """ |
| 193 | try: |
| 194 | component = getattr( main, name ) |
| 195 | except AttributeError: |
| 196 | main.log.error( "Component " + name + " does not exist." ) |
| 197 | return main.FALSE |
| 198 | try: |
| 199 | # Disconnect from component |
| 200 | component.disconnect() |
| 201 | # Delete component |
| 202 | delattr( main, name ) |
| 203 | # Delete component from ComponentDictionary |
| 204 | del( main.componentDictionary[ name ] ) |
| 205 | return main.TRUE |
| 206 | except Exception: |
| 207 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 208 | main.cleanAndExit() |
| 209 | |
| 210 | def createComponent( self, name ): |
| 211 | """ |
| 212 | Creates switch/host component with the same parameters as the one copied to local. |
| 213 | Arguments: |
| 214 | name - The string of the name of this component. The new component |
| 215 | will be assigned to main.<name> . |
| 216 | In addition, main.<name>.name = str( name ) |
| 217 | """ |
| 218 | try: |
| 219 | # look to see if this component already exists |
| 220 | getattr( main, name ) |
| 221 | except AttributeError: |
| 222 | # namespace is clear, creating component |
| 223 | localName = self.name + "-" + name |
| 224 | main.componentDictionary[ name ] = main.componentDictionary[ localName ].copy() |
| 225 | main.componentInit( name ) |
| 226 | except Exception: |
| 227 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 228 | main.cleanAndExit() |
| 229 | else: |
| 230 | # namespace is not clear! |
| 231 | main.log.error( name + " component already exists!" ) |
| 232 | main.cleanAndExit() |
| 233 | |
| 234 | def connectInbandHosts( self ): |
| 235 | """ |
| 236 | Connect to hosts using data plane IPs specified |
| 237 | """ |
| 238 | result = main.TRUE |
| 239 | try: |
| 240 | for hostName, hostComponent in self.hosts.items(): |
| 241 | if hostComponent.options[ 'inband' ] == 'True': |
| 242 | main.log.info( self.name + ": connecting inband host " + hostName ) |
| 243 | result = hostComponent.connectInband() and result |
| 244 | return result |
| 245 | except Exception: |
| 246 | main.log.error( self.name + ": failed to connect to inband hosts" ) |
| 247 | return main.FALSE |
| 248 | |
| 249 | def disconnectInbandHosts( self ): |
| 250 | """ |
| 251 | Terminate the connections to hosts using data plane IPs |
| 252 | """ |
| 253 | result = main.TRUE |
| 254 | try: |
| 255 | for hostName, hostComponent in self.hosts.items(): |
| 256 | if hostComponent.options[ 'inband' ] == 'True': |
| 257 | main.log.info( self.name + ": disconnecting inband host " + hostName ) |
| 258 | result = hostComponent.disconnectInband() and result |
| 259 | return result |
| 260 | except Exception: |
| 261 | main.log.error( self.name + ": failed to disconnect inband hosts" ) |
| 262 | return main.FALSE |
| 263 | |
| 264 | def getSwitches( self ): |
| 265 | """ |
| 266 | Return a dictionary which maps short names to switch component |
| 267 | """ |
| 268 | return self.switches |
| 269 | |
| 270 | def getHosts( self ): |
| 271 | """ |
| 272 | Return a dictionary which maps short names to host component |
| 273 | """ |
| 274 | return self.hosts |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 275 | |
| 276 | def getMacAddress( self, host ): |
| 277 | """ |
| 278 | Return MAC address of a host |
| 279 | """ |
| 280 | import re |
| 281 | try: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 282 | hosts = self.getHosts() |
| 283 | hostComponent = hosts[ host ] |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 284 | response = hostComponent.ifconfig() |
| 285 | pattern = r'HWaddr\s([0-9A-F]{2}[:-]){5}([0-9A-F]{2})' |
| 286 | macAddressSearch = re.search( pattern, response, re.I ) |
| 287 | macAddress = macAddressSearch.group().split( " " )[ 1 ] |
| 288 | main.log.info( self.name + ": Mac-Address of Host " + host + " is " + macAddress ) |
| 289 | return macAddress |
| 290 | except Exception: |
| 291 | main.log.error( self.name + ": failed to get host MAC address" ) |
| 292 | |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 293 | def runCmdOnHost( self, hostName, cmd ): |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 294 | """ |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 295 | Run shell command on specified host and return output |
| 296 | Required: |
| 297 | hostName: name of the host e.g. "h1" |
| 298 | cmd: command to run on the host |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 299 | """ |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 300 | hosts = self.getHosts() |
| 301 | hostComponent = hosts[ hostName ] |
| 302 | if hostComponent: |
| 303 | return hostComponent.command( cmd ) |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 304 | return None |
| 305 | |
| 306 | def assignSwController( self, sw, ip, port="6653", ptcp="" ): |
| 307 | """ |
| 308 | Description: |
| 309 | Assign switches to the controllers |
| 310 | Required: |
| 311 | sw - Short name of the switch specified in the .topo file, e.g. "s1". |
| 312 | It can also be a list of switch names. |
| 313 | ip - Ip addresses of controllers. This can be a list or a string. |
| 314 | Optional: |
| 315 | port - ONOS use port 6653, if no list of ports is passed, then |
| 316 | the all the controller will use 6653 as their port number |
| 317 | ptcp - ptcp number, This can be a string or a list that has |
| 318 | the same length as switch. This is optional and not required |
| 319 | when using ovs switches. |
| 320 | NOTE: If switches and ptcp are given in a list type they should have the |
| 321 | same length and should be in the same order, Eg. sw=[ 's1' ... n ] |
| 322 | ptcp=[ '6637' ... n ], s1 has ptcp number 6637 and so on. |
| 323 | |
| 324 | Return: |
| 325 | Returns main.TRUE if switches are correctly assigned to controllers, |
| 326 | otherwise it will return main.FALSE or an appropriate exception(s) |
| 327 | """ |
| 328 | switchList = [] |
| 329 | ptcpList = None |
| 330 | try: |
| 331 | if isinstance( sw, types.StringType ): |
| 332 | switchList.append( sw ) |
| 333 | if ptcp: |
| 334 | if isinstance( ptcp, types.StringType ): |
| 335 | ptcpList = [ ptcp ] |
| 336 | elif isinstance( ptcp, types.ListType ): |
| 337 | main.log.error( self.name + ": Only one switch is " + |
| 338 | "being set and multiple PTCP is " + |
| 339 | "being passed " ) |
| 340 | return main.FALSE |
| 341 | else: |
| 342 | main.log.error( self.name + ": Invalid PTCP" ) |
| 343 | return main.FALSE |
| 344 | |
| 345 | elif isinstance( sw, types.ListType ): |
| 346 | switchList = sw |
| 347 | if ptcp: |
| 348 | if isinstance( ptcp, types.ListType ): |
| 349 | if len( ptcp ) != len( sw ): |
| 350 | main.log.error( self.name + ": PTCP length = " + |
| 351 | str( len( ptcp ) ) + |
| 352 | " is not the same as switch" + |
| 353 | " length = " + |
| 354 | str( len( sw ) ) ) |
| 355 | return main.FALSE |
| 356 | else: |
| 357 | ptcpList = ptcp |
| 358 | else: |
| 359 | main.log.error( self.name + ": Invalid PTCP" ) |
| 360 | return main.FALSE |
| 361 | else: |
| 362 | main.log.error( self.name + ": Invalid switch type " ) |
| 363 | return main.FALSE |
| 364 | |
| 365 | assignResult = main.TRUE |
| 366 | index = 0 |
| 367 | for switch in switchList: |
| 368 | assigned = False |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 369 | switchComponent = self.switches[ switch ] |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 370 | if switchComponent: |
| 371 | ptcp = ptcpList[ index ] if ptcpList else "" |
| 372 | assignResult = assignResult and switchComponent.assignSwController( ip=ip, port=port, ptcp=ptcp ) |
| 373 | assigned = True |
| 374 | if not assigned: |
| 375 | main.log.error( self.name + ": Not able to find switch " + switch ) |
| 376 | assignResult = main.FALSE |
| 377 | index += 1 |
| 378 | return assignResult |
| 379 | |
| 380 | except Exception: |
| 381 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 382 | main.cleanAndExit() |
| 383 | |
| 384 | def pingall( self, protocol="IPv4", timeout=300, shortCircuit=False, acceptableFailed=0 ): |
| 385 | """ |
| 386 | Description: |
| 387 | Verifies the reachability of the hosts using ping command. |
| 388 | Optional: |
| 389 | protocol - use ping6 command if specified as "IPv6" |
| 390 | timeout( seconds ) - How long to wait before breaking the pingall |
| 391 | shortCircuit - Break the pingall based on the number of failed hosts ping |
| 392 | acceptableFailed - Set the number of acceptable failed pings for the |
| 393 | function to still return main.TRUE |
| 394 | Returns: |
| 395 | main.TRUE if pingall completes with no pings dropped |
| 396 | otherwise main.FALSE |
| 397 | """ |
| 398 | import time |
| 399 | import itertools |
| 400 | try: |
| 401 | timeout = int( timeout ) |
| 402 | main.log.info( self.name + ": Checking reachabilty to the hosts using ping" ) |
| 403 | failedPings = 0 |
| 404 | returnValue = main.TRUE |
| 405 | ipv6 = True if protocol == "IPv6" else False |
| 406 | startTime = time.time() |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 407 | hosts = self.getHosts() |
| 408 | hostPairs = itertools.permutations( list( hosts.values() ), 2 ) |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 409 | for hostPair in list( hostPairs ): |
| 410 | ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ] |
| 411 | pingResult = hostPair[ 0 ].ping( ipDst, ipv6=ipv6 ) |
| 412 | returnValue = returnValue and pingResult |
| 413 | if ( time.time() - startTime ) > timeout: |
| 414 | returnValue = main.FALSE |
| 415 | main.log.error( self.name + |
| 416 | ": Aborting pingall - " + |
| 417 | "Function took too long " ) |
| 418 | break |
| 419 | if not pingResult: |
| 420 | failedPings = failedPings + 1 |
| 421 | if failedPings > acceptableFailed: |
| 422 | returnValue = main.FALSE |
| 423 | if shortCircuit: |
| 424 | main.log.error( self.name + |
| 425 | ": Aborting pingall - " |
| 426 | + str( failedPings ) + |
| 427 | " pings failed" ) |
| 428 | break |
| 429 | return returnValue |
| 430 | except Exception: |
| 431 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 432 | main.cleanAndExit() |
| 433 | |
| 434 | def pingallHosts( self, hostList, wait=1 ): |
| 435 | """ |
| 436 | Ping all specified IPv4 hosts |
| 437 | |
| 438 | Acceptable hostList: |
| 439 | - [ 'h1','h2','h3','h4' ] |
| 440 | |
| 441 | Returns main.TRUE if all hosts specified can reach |
| 442 | each other |
| 443 | |
| 444 | Returns main.FALSE if one or more of hosts specified |
| 445 | cannot reach each other""" |
| 446 | import time |
| 447 | import itertools |
| 448 | hostComponentList = [] |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 449 | hosts = self.getHosts() |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 450 | for hostName in hostList: |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 451 | hostComponent = hosts[ hostName ] |
You Wang | 84f981d | 2018-01-12 16:11:50 -0800 | [diff] [blame] | 452 | if hostComponent: |
| 453 | hostComponentList.append( hostComponent ) |
| 454 | try: |
| 455 | main.log.info( "Testing reachability between specified hosts" ) |
| 456 | isReachable = main.TRUE |
| 457 | pingResponse = "IPv4 ping across specified hosts\n" |
| 458 | failedPings = 0 |
| 459 | hostPairs = itertools.permutations( list( hostComponentList ), 2 ) |
| 460 | for hostPair in list( hostPairs ): |
| 461 | pingResponse += hostPair[ 0 ].options[ 'shortName' ] + " -> " |
| 462 | ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ] |
| 463 | pingResult = hostPair[ 0 ].ping( ipDst, wait=int( wait ) ) |
| 464 | if pingResult: |
| 465 | pingResponse += hostPair[ 1 ].options[ 'shortName' ] |
| 466 | else: |
| 467 | pingResponse += "X" |
| 468 | # One of the host to host pair is unreachable |
| 469 | isReachable = main.FALSE |
| 470 | failedPings += 1 |
| 471 | pingResponse += "\n" |
| 472 | main.log.info( pingResponse + "Failed pings: " + str( failedPings ) ) |
| 473 | return isReachable |
| 474 | except Exception: |
| 475 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 476 | main.cleanAndExit() |
| 477 | |
| 478 | def iperftcp( self, host1="h1", host2="h2", timeout=6 ): |
| 479 | ''' |
| 480 | Creates an iperf TCP test between two hosts. Returns main.TRUE if test results |
| 481 | are valid. |
| 482 | Optional: |
| 483 | timeout: The defualt timeout is 6 sec to allow enough time for a successful test to complete, |
| 484 | and short enough to stop an unsuccessful test from quiting and cleaning up mininet. |
| 485 | ''' |
| 486 | main.log.info( self.name + ": Simple iperf TCP test between two hosts" ) |
| 487 | # TODO: complete this function |
| 488 | return main.TRUE |
You Wang | 4cc6191 | 2018-08-28 10:10:58 -0700 | [diff] [blame] | 489 | |
| 490 | def update( self ): |
| 491 | return main.TRUE |
| 492 | |
| 493 | def verifyHostIp( self, hostList=[], prefix="", update=False ): |
| 494 | """ |
| 495 | Description: |
| 496 | Verify that all hosts have IP address assigned to them |
| 497 | Optional: |
| 498 | hostList: If specified, verifications only happen to the hosts |
| 499 | in hostList |
| 500 | prefix: at least one of the ip address assigned to the host |
| 501 | needs to have the specified prefix |
| 502 | Returns: |
| 503 | main.TRUE if all hosts have specific IP address assigned; |
| 504 | main.FALSE otherwise |
| 505 | """ |
| 506 | try: |
| 507 | hosts = self.getHosts() |
| 508 | if not hostList: |
| 509 | hostList = hosts.keys() |
| 510 | for hostName, hostComponent in hosts.items(): |
| 511 | if hostName not in hostList: |
| 512 | continue |
| 513 | ipList = [] |
| 514 | ipa = hostComponent.ip() |
| 515 | ipv4Pattern = r'inet ((?:[0-9]{1,3}\.){3}[0-9]{1,3})/' |
| 516 | ipList += re.findall( ipv4Pattern, ipa ) |
| 517 | # It's tricky to make regex for IPv6 addresses and this one is simplified |
| 518 | 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})/' |
| 519 | ipList += re.findall( ipv6Pattern, ipa ) |
| 520 | main.log.debug( self.name + ": IP list on host " + str( hostName ) + ": " + str( ipList ) ) |
| 521 | if not ipList: |
| 522 | main.log.warn( self.name + ": Failed to discover any IP addresses on host " + str( hostName ) ) |
| 523 | else: |
| 524 | if not any( ip.startswith( str( prefix ) ) for ip in ipList ): |
| 525 | main.log.warn( self.name + ": None of the IPs on host " + str( hostName ) + " has prefix " + str( prefix ) ) |
| 526 | else: |
| 527 | main.log.debug( self.name + ": Found matching IP on host " + str( hostName ) ) |
| 528 | hostList.remove( hostName ) |
| 529 | return main.FALSE if hostList else main.TRUE |
| 530 | except KeyError: |
| 531 | main.log.exception( self.name + ": host data not as expected: " + hosts ) |
| 532 | return None |
| 533 | except pexpect.EOF: |
| 534 | main.log.error( self.name + ": EOF exception found" ) |
| 535 | main.log.error( self.name + ": " + self.handle.before ) |
| 536 | main.cleanAndExit() |
| 537 | except Exception: |
| 538 | main.log.exception( self.name + ": Uncaught exception" ) |
| 539 | return None |
| 540 | |
| 541 | def addRoute( self, host, dstIP, interface, ipv6=False ): |
| 542 | """ |
| 543 | Add a route to host |
| 544 | Ex: h1 route add -host 224.2.0.1 h1-eth0 |
| 545 | """ |
| 546 | try: |
| 547 | if ipv6: |
| 548 | cmd = "sudo route -A inet6 add " |
| 549 | else: |
| 550 | cmd = "sudo route add -host " |
| 551 | cmd += str( dstIP ) + " " + str( interface ) |
| 552 | response = self.runCmdOnHost( host, cmd ) |
| 553 | main.log.debug( "response = " + response ) |
| 554 | return main.TRUE |
| 555 | except pexpect.TIMEOUT: |
| 556 | main.log.error( self.name + ": TIMEOUT exception found" ) |
| 557 | main.log.error( self.name + ": " + self.handle.before ) |
| 558 | main.cleanAndExit() |
| 559 | except pexpect.EOF: |
| 560 | main.log.error( self.name + ": EOF exception found" ) |
| 561 | main.log.error( self.name + ": " + self.handle.before ) |
| 562 | return main.FALSE |
| 563 | except Exception: |
| 564 | main.log.exception( self.name + ": Uncaught exception!" ) |
| 565 | main.cleanAndExit() |