blob: d1a7fafc3616ee651aa87a7529f47994ca39cb44 [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
32import types
33from drivers.common.clidriver import CLI
34
35class NetworkDriver( CLI ):
36
37 def __init__( self ):
38 """
39 switches: a dictionary that maps switch names to components
40 hosts: a dictionary that maps host names to components
41 """
42 self.name = None
43 self.home = None
44 self.handle = None
45 self.switches = {}
46 self.hosts = {}
47 super( NetworkDriver, self ).__init__()
48
49 def checkOptions( self, var, defaultVar ):
50 if var is None or var == "":
51 return defaultVar
52 return var
53
54 def connect( self, **connectargs ):
55 """
56 Creates ssh handle for the SDN network "bench".
57 NOTE:
58 The ip_address would come from the topo file using the host tag, the
59 value can be an environment variable as well as a "localhost" to get
60 the ip address needed to ssh to the "bench"
61 """
62 try:
63 for key in connectargs:
64 vars( self )[ key ] = connectargs[ key ]
65 self.name = self.options[ 'name' ]
66 try:
67 if os.getenv( str( self.ip_address ) ) is not None:
68 self.ip_address = os.getenv( str( self.ip_address ) )
69 else:
70 main.log.info( self.name +
71 ": Trying to connect to " +
72 self.ip_address )
73 except KeyError:
74 main.log.info( "Invalid host name," +
75 " connecting to local host instead" )
76 self.ip_address = 'localhost'
77 except Exception as inst:
78 main.log.error( "Uncaught exception: " + str( inst ) )
79
80 self.handle = super( NetworkDriver, self ).connect(
81 user_name=self.user_name,
82 ip_address=self.ip_address,
83 port=self.port,
84 pwd=self.pwd )
85
86 if self.handle:
87 main.log.info( "Connected to network bench node" )
88 return self.handle
89 else:
90 main.log.info( "Failed to create handle" )
91 return main.FALSE
92 except pexpect.EOF:
93 main.log.error( self.name + ": EOF exception found" )
94 main.log.error( self.name + ": " + self.handle.before )
95 main.cleanAndExit()
96 except Exception:
97 main.log.exception( self.name + ": Uncaught exception!" )
98 main.cleanAndExit()
99
100 def disconnect( self ):
101 """
102 Called when test is complete to disconnect the handle.
103 """
104 response = main.TRUE
105 try:
106 if self.handle:
107 self.handle.sendline( "exit" )
108 self.handle.expect( "closed" )
109 except pexpect.EOF:
110 main.log.error( self.name + ": EOF exception found" )
111 main.log.error( self.name + ": " + self.handle.before )
112 except Exception:
113 main.log.exception( self.name + ": Connection failed to the host" )
114 response = main.FALSE
115 return response
116
117 def connectToNet( self ):
118 """
119 Connect to an existing physical network by getting information
120 of all switch and host components created
121 """
122 try:
123 for key, value in main.componentDictionary.items():
124 if hasattr( main, key ):
125 if value[ 'type' ] in [ 'MininetSwitchDriver' ]:
126 self.switches[ key ] = getattr( main, key )
127 elif value[ 'type' ] in [ 'MininetHostDriver' ]:
128 self.hosts[ key ] = getattr( main, key )
129 return main.TRUE
130 except Exception:
131 main.log.error( self.name + ": failed to connect to network" )
132 return main.FALSE
133
134 def getHosts( self, verbose=False, updateTimeout=1000 ):
135 """
136 Return a dictionary which maps short names to host data
137 """
138 hosts = {}
139 try:
140 for hostComponent in self.hosts.values():
141 #TODO: return more host data
142 hosts[ hostComponent.options[ 'shortName' ] ] = {}
143 except Exception:
144 main.log.error( self.name + ": host component not as expected" )
145 return hosts
146
147 def getMacAddress( self, host ):
148 """
149 Return MAC address of a host
150 """
151 import re
152 try:
153 hostComponent = self.getHostComponentByShortName( host )
154 response = hostComponent.ifconfig()
155 pattern = r'HWaddr\s([0-9A-F]{2}[:-]){5}([0-9A-F]{2})'
156 macAddressSearch = re.search( pattern, response, re.I )
157 macAddress = macAddressSearch.group().split( " " )[ 1 ]
158 main.log.info( self.name + ": Mac-Address of Host " + host + " is " + macAddress )
159 return macAddress
160 except Exception:
161 main.log.error( self.name + ": failed to get host MAC address" )
162
163 def getSwitchComponentByShortName( self, shortName ):
164 """
165 Get switch component by its short name i.e. "s1"
166 """
167 for switchComponent in self.switches.values():
168 if switchComponent.options[ 'shortName' ] == shortName:
169 return switchComponent
170 main.log.warn( self.name + ": failed to find switch component by name " + shortName )
171 return None
172
173 def getHostComponentByShortName( self, shortName ):
174 """
175 Get host component by its short name i.e. "h1"
176 """
177 for hostComponent in self.hosts.values():
178 if hostComponent.options[ 'shortName' ] == shortName:
179 return hostComponent
180 main.log.warn( self.name + ": failed to find host component by name " + shortName )
181 return None
182
183 def assignSwController( self, sw, ip, port="6653", ptcp="" ):
184 """
185 Description:
186 Assign switches to the controllers
187 Required:
188 sw - Short name of the switch specified in the .topo file, e.g. "s1".
189 It can also be a list of switch names.
190 ip - Ip addresses of controllers. This can be a list or a string.
191 Optional:
192 port - ONOS use port 6653, if no list of ports is passed, then
193 the all the controller will use 6653 as their port number
194 ptcp - ptcp number, This can be a string or a list that has
195 the same length as switch. This is optional and not required
196 when using ovs switches.
197 NOTE: If switches and ptcp are given in a list type they should have the
198 same length and should be in the same order, Eg. sw=[ 's1' ... n ]
199 ptcp=[ '6637' ... n ], s1 has ptcp number 6637 and so on.
200
201 Return:
202 Returns main.TRUE if switches are correctly assigned to controllers,
203 otherwise it will return main.FALSE or an appropriate exception(s)
204 """
205 switchList = []
206 ptcpList = None
207 try:
208 if isinstance( sw, types.StringType ):
209 switchList.append( sw )
210 if ptcp:
211 if isinstance( ptcp, types.StringType ):
212 ptcpList = [ ptcp ]
213 elif isinstance( ptcp, types.ListType ):
214 main.log.error( self.name + ": Only one switch is " +
215 "being set and multiple PTCP is " +
216 "being passed " )
217 return main.FALSE
218 else:
219 main.log.error( self.name + ": Invalid PTCP" )
220 return main.FALSE
221
222 elif isinstance( sw, types.ListType ):
223 switchList = sw
224 if ptcp:
225 if isinstance( ptcp, types.ListType ):
226 if len( ptcp ) != len( sw ):
227 main.log.error( self.name + ": PTCP length = " +
228 str( len( ptcp ) ) +
229 " is not the same as switch" +
230 " length = " +
231 str( len( sw ) ) )
232 return main.FALSE
233 else:
234 ptcpList = ptcp
235 else:
236 main.log.error( self.name + ": Invalid PTCP" )
237 return main.FALSE
238 else:
239 main.log.error( self.name + ": Invalid switch type " )
240 return main.FALSE
241
242 assignResult = main.TRUE
243 index = 0
244 for switch in switchList:
245 assigned = False
246 switchComponent = self.getSwitchComponentByShortName( switch )
247 if switchComponent:
248 ptcp = ptcpList[ index ] if ptcpList else ""
249 assignResult = assignResult and switchComponent.assignSwController( ip=ip, port=port, ptcp=ptcp )
250 assigned = True
251 if not assigned:
252 main.log.error( self.name + ": Not able to find switch " + switch )
253 assignResult = main.FALSE
254 index += 1
255 return assignResult
256
257 except Exception:
258 main.log.exception( self.name + ": Uncaught exception!" )
259 main.cleanAndExit()
260
261 def pingall( self, protocol="IPv4", timeout=300, shortCircuit=False, acceptableFailed=0 ):
262 """
263 Description:
264 Verifies the reachability of the hosts using ping command.
265 Optional:
266 protocol - use ping6 command if specified as "IPv6"
267 timeout( seconds ) - How long to wait before breaking the pingall
268 shortCircuit - Break the pingall based on the number of failed hosts ping
269 acceptableFailed - Set the number of acceptable failed pings for the
270 function to still return main.TRUE
271 Returns:
272 main.TRUE if pingall completes with no pings dropped
273 otherwise main.FALSE
274 """
275 import time
276 import itertools
277 try:
278 timeout = int( timeout )
279 main.log.info( self.name + ": Checking reachabilty to the hosts using ping" )
280 failedPings = 0
281 returnValue = main.TRUE
282 ipv6 = True if protocol == "IPv6" else False
283 startTime = time.time()
284 hostPairs = itertools.permutations( list( self.hosts.values() ), 2 )
285 for hostPair in list( hostPairs ):
286 ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ]
287 pingResult = hostPair[ 0 ].ping( ipDst, ipv6=ipv6 )
288 returnValue = returnValue and pingResult
289 if ( time.time() - startTime ) > timeout:
290 returnValue = main.FALSE
291 main.log.error( self.name +
292 ": Aborting pingall - " +
293 "Function took too long " )
294 break
295 if not pingResult:
296 failedPings = failedPings + 1
297 if failedPings > acceptableFailed:
298 returnValue = main.FALSE
299 if shortCircuit:
300 main.log.error( self.name +
301 ": Aborting pingall - "
302 + str( failedPings ) +
303 " pings failed" )
304 break
305 return returnValue
306 except Exception:
307 main.log.exception( self.name + ": Uncaught exception!" )
308 main.cleanAndExit()
309
310 def pingallHosts( self, hostList, wait=1 ):
311 """
312 Ping all specified IPv4 hosts
313
314 Acceptable hostList:
315 - [ 'h1','h2','h3','h4' ]
316
317 Returns main.TRUE if all hosts specified can reach
318 each other
319
320 Returns main.FALSE if one or more of hosts specified
321 cannot reach each other"""
322 import time
323 import itertools
324 hostComponentList = []
325 for hostName in hostList:
326 hostComponent = self.getHostComponentByShortName( hostName )
327 if hostComponent:
328 hostComponentList.append( hostComponent )
329 try:
330 main.log.info( "Testing reachability between specified hosts" )
331 isReachable = main.TRUE
332 pingResponse = "IPv4 ping across specified hosts\n"
333 failedPings = 0
334 hostPairs = itertools.permutations( list( hostComponentList ), 2 )
335 for hostPair in list( hostPairs ):
336 pingResponse += hostPair[ 0 ].options[ 'shortName' ] + " -> "
337 ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ]
338 pingResult = hostPair[ 0 ].ping( ipDst, wait=int( wait ) )
339 if pingResult:
340 pingResponse += hostPair[ 1 ].options[ 'shortName' ]
341 else:
342 pingResponse += "X"
343 # One of the host to host pair is unreachable
344 isReachable = main.FALSE
345 failedPings += 1
346 pingResponse += "\n"
347 main.log.info( pingResponse + "Failed pings: " + str( failedPings ) )
348 return isReachable
349 except Exception:
350 main.log.exception( self.name + ": Uncaught exception!" )
351 main.cleanAndExit()
352
353 def iperftcp( self, host1="h1", host2="h2", timeout=6 ):
354 '''
355 Creates an iperf TCP test between two hosts. Returns main.TRUE if test results
356 are valid.
357 Optional:
358 timeout: The defualt timeout is 6 sec to allow enough time for a successful test to complete,
359 and short enough to stop an unsuccessful test from quiting and cleaning up mininet.
360 '''
361 main.log.info( self.name + ": Simple iperf TCP test between two hosts" )
362 # TODO: complete this function
363 return main.TRUE