blob: 928db71e255c6441d0cce92f52b6d98cd57ef30b [file] [log] [blame]
adminbae64d82013-08-01 10:50:15 -07001#!/usr/bin/env python
kelvin8ec71442015-01-15 16:57:00 -08002"""
adminbae64d82013-08-01 10:50:15 -07003Created on 24-Oct-2012
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +00004Copyright 2012 Open Networking Foundation (ONF)
kelvin8ec71442015-01-15 16:57:00 -08005
Jeremy Songsterae01bba2016-07-11 15:39:17 -07006Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
7the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
8or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
adminbae64d82013-08-01 10:50:15 -07009
10 TestON is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 2 of the License, or
Jeremy Ronquillo82705492017-10-18 14:19:55 -070013 (at your option) any later version.
adminbae64d82013-08-01 10:50:15 -070014
15 TestON is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
kelvin8ec71442015-01-15 16:57:00 -080021 along with TestON. If not, see <http://www.gnu.org/licenses/>.
kelvin8ec71442015-01-15 16:57:00 -080022"""
adminbae64d82013-08-01 10:50:15 -070023import pexpect
kelvin8ec71442015-01-15 16:57:00 -080024import re
adminbae64d82013-08-01 10:50:15 -070025
26from drivers.component import Component
kelvin8ec71442015-01-15 16:57:00 -080027
28
29class CLI( Component ):
30
31 """
adminbae64d82013-08-01 10:50:15 -070032 This will define common functions for CLI included.
kelvin8ec71442015-01-15 16:57:00 -080033 """
34 def __init__( self ):
Devin Limdc78e202017-06-09 18:30:07 -070035 super( CLI, self ).__init__()
Jon Hall3c0114c2020-08-11 15:07:42 -070036 self.inDocker = False
Jon Hallca319892017-06-15 15:25:22 -070037
Jeremy Ronquillo82705492017-10-18 14:19:55 -070038 def checkPrompt( self ):
Devin Limdc78e202017-06-09 18:30:07 -070039 for key in self.options:
Jeremy Ronquillo82705492017-10-18 14:19:55 -070040 if key == "prompt" and self.options[ 'prompt' ] is not None:
41 self.prompt = self.options[ 'prompt' ]
Devin Limdc78e202017-06-09 18:30:07 -070042 break
kelvin8ec71442015-01-15 16:57:00 -080043
44 def connect( self, **connectargs ):
45 """
adminbae64d82013-08-01 10:50:15 -070046 Connection will establish to the remote host using ssh.
47 It will take user_name ,ip_address and password as arguments<br>
kelvin8ec71442015-01-15 16:57:00 -080048 and will return the handle.
49 """
adminbae64d82013-08-01 10:50:15 -070050 for key in connectargs:
kelvin8ec71442015-01-15 16:57:00 -080051 vars( self )[ key ] = connectargs[ key ]
Devin Limdc78e202017-06-09 18:30:07 -070052 self.checkPrompt()
adminbae64d82013-08-01 10:50:15 -070053
kelvin8ec71442015-01-15 16:57:00 -080054 connect_result = super( CLI, self ).connect()
adminbae64d82013-08-01 10:50:15 -070055 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -080056 refused = "ssh: connect to host " + \
57 self.ip_address + " port 22: Connection refused"
adminbae64d82013-08-01 10:50:15 -070058 if self.port:
kelvin8ec71442015-01-15 16:57:00 -080059 self.handle = pexpect.spawn(
Jon Hall7a5e0a22017-12-11 10:35:50 -080060 'ssh -X -p ' +
kelvin8ec71442015-01-15 16:57:00 -080061 self.port +
62 ' ' +
63 self.user_name +
64 '@' +
Jon Hall7a5e0a22017-12-11 10:35:50 -080065 self.ip_address +
66 ' -o ServerAliveInterval=120 -o TCPKeepAlive=yes',
Jon Hall6c9e2da2018-11-06 12:01:23 -080067 env={ "TERM": "vt100" },
Jon Hall7a5e0a22017-12-11 10:35:50 -080068 maxread=1000000 )
kelvin8ec71442015-01-15 16:57:00 -080069 else:
70 self.handle = pexpect.spawn(
71 'ssh -X ' +
72 self.user_name +
73 '@' +
Jon Hall7a5e0a22017-12-11 10:35:50 -080074 self.ip_address +
75 ' -o ServerAliveInterval=120 -o TCPKeepAlive=yes',
Jon Hall6c9e2da2018-11-06 12:01:23 -080076 env={ "TERM": "vt100" },
kelvin8ec71442015-01-15 16:57:00 -080077 maxread=1000000,
78 timeout=60 )
adminbae64d82013-08-01 10:50:15 -070079
Jon Hall73057ee2016-08-23 09:57:26 -070080 # set tty window size
81 self.handle.setwinsize( 24, 250 )
82
adminbae64d82013-08-01 10:50:15 -070083 self.handle.logfile = self.logfile_handler
kelvin8ec71442015-01-15 16:57:00 -080084 i = 5
85 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -070086 i = self.handle.expect( [ ssh_newkey,
87 'password:|Password:',
88 pexpect.EOF,
89 pexpect.TIMEOUT,
90 refused,
91 'teston>',
Jon Halld9066132018-03-01 14:52:53 -080092 'Permission denied, please try again.',
Jon Hall4173b242017-09-12 17:04:38 -070093 self.prompt ],
94 120 )
acsmars32de0bc2015-06-30 09:57:12 -070095 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -070096 main.log.info( self.name + ": ssh key confirmation received, send yes" )
kelvin8ec71442015-01-15 16:57:00 -080097 self.handle.sendline( 'yes' )
acsmars32de0bc2015-06-30 09:57:12 -070098 i = 5 # Run the loop again
acsmars07f9d392015-07-15 10:30:58 -070099 continue
100 if i == 1: # Password required
Jon Hall63604932015-02-26 17:09:50 -0800101 if self.pwd:
102 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700103 "ssh connection asked for password, gave password" )
Jon Hall63604932015-02-26 17:09:50 -0800104 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700105 main.log.info( self.name + ": Server asked for password, but none was "
acsmars07f9d392015-07-15 10:30:58 -0700106 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700107 "no password." )
acsmars07f9d392015-07-15 10:30:58 -0700108 self.pwd = ""
109 self.handle.sendline( self.pwd )
110 j = self.handle.expect( [
acsmars07f9d392015-07-15 10:30:58 -0700111 'password:|Password:',
Jon Halld9066132018-03-01 14:52:53 -0800112 'Permission denied, please try again.',
113 self.prompt,
acsmars07f9d392015-07-15 10:30:58 -0700114 pexpect.EOF,
115 pexpect.TIMEOUT ],
116 120 )
Jon Halld9066132018-03-01 14:52:53 -0800117 if j != 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700118 main.log.error( self.name + ": Incorrect Password" )
acsmars07f9d392015-07-15 10:30:58 -0700119 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800120 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700121 main.log.error( self.name + ": Connection timeout" )
kelvin8ec71442015-01-15 16:57:00 -0800122 return main.FALSE
123 elif i == 3: # timeout
124 main.log.error(
125 "No route to the Host " +
126 self.user_name +
127 "@" +
128 self.ip_address )
129 return main.FALSE
130 elif i == 4:
131 main.log.error(
132 "ssh: connect to host " +
133 self.ip_address +
134 " port 22: Connection refused" )
135 return main.FALSE
Jon Halld9066132018-03-01 14:52:53 -0800136 elif i == 6: # Incorrect Password
Jon Hall3c0114c2020-08-11 15:07:42 -0700137 main.log.error( self.name + ": Incorrect Password" )
Jon Halld9066132018-03-01 14:52:53 -0800138 return main.FALSE
139 elif i == 7: # Prompt
Jon Hall3c0114c2020-08-11 15:07:42 -0700140 main.log.info( self.name + ": Password not required logged in" )
adminbae64d82013-08-01 10:50:15 -0700141
kelvin8ec71442015-01-15 16:57:00 -0800142 self.handle.sendline( "" )
Devin Limdc78e202017-06-09 18:30:07 -0700143 self.handle.expect( self.prompt )
Jeremy Ronquillo0f2008a2017-06-23 15:32:51 -0700144 self.handle.sendline( "cd" )
145 self.handle.expect( self.prompt )
adminbae64d82013-08-01 10:50:15 -0700146 return self.handle
147
kelvin8ec71442015-01-15 16:57:00 -0800148 def disconnect( self ):
149 result = super( CLI, self ).disconnect( self )
adminbae64d82013-08-01 10:50:15 -0700150 result = main.TRUE
Jon Hall3c0114c2020-08-11 15:07:42 -0700151
152 def Prompt( self ):
153 """
154 Returns the prompt to expect depending on what program we are in
155 """
156 return self.prompt if not self.inDocker else self.dockerPrompt
kelvin8ec71442015-01-15 16:57:00 -0800157
158 def execute( self, **execparams ):
159 """
adminbae64d82013-08-01 10:50:15 -0700160 It facilitates the command line execution of a given command. It has arguments as :
161 cmd => represents command to be executed,
162 prompt => represents expect command prompt or output,
163 timeout => timeout for command execution,
164 more => to provide a key press if it is on.
You Wang7d14d642019-01-23 15:10:08 -0800165 logCmd => log the command executed if True
adminbae64d82013-08-01 10:50:15 -0700166
167 It will return output of command exection.
kelvin8ec71442015-01-15 16:57:00 -0800168 """
169 result = super( CLI, self ).execute( self )
adminaef00552014-05-08 09:18:36 -0700170 defaultPrompt = '.*[$>\#]'
Jon Hall3b489db2015-10-05 14:38:37 -0700171 args = utilities.parse_args( [ "CMD",
172 "TIMEOUT",
173 "PROMPT",
You Wang7d14d642019-01-23 15:10:08 -0800174 "MORE",
175 "LOGCMD" ],
Jon Hall3b489db2015-10-05 14:38:37 -0700176 **execparams )
kelvin8ec71442015-01-15 16:57:00 -0800177
178 expectPrompt = args[ "PROMPT" ] if args[ "PROMPT" ] else defaultPrompt
adminbae64d82013-08-01 10:50:15 -0700179 self.LASTRSP = ""
kelvin8ec71442015-01-15 16:57:00 -0800180 timeoutVar = args[ "TIMEOUT" ] if args[ "TIMEOUT" ] else 10
adminbae64d82013-08-01 10:50:15 -0700181 cmd = ''
kelvin8ec71442015-01-15 16:57:00 -0800182 if args[ "CMD" ]:
183 cmd = args[ "CMD" ]
184 else:
adminbae64d82013-08-01 10:50:15 -0700185 return 0
kelvin8ec71442015-01-15 16:57:00 -0800186 if args[ "MORE" ] is None:
187 args[ "MORE" ] = " "
188 self.handle.sendline( cmd )
adminbae64d82013-08-01 10:50:15 -0700189 self.lastCommand = cmd
Jon Hall3b489db2015-10-05 14:38:37 -0700190 index = self.handle.expect( [ expectPrompt,
191 "--More--",
192 'Command not found.',
193 pexpect.TIMEOUT,
194 "^:$" ],
195 timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700196 if index == 0:
kelvin8ec71442015-01-15 16:57:00 -0800197 self.LASTRSP = self.LASTRSP + \
198 self.handle.before + self.handle.after
You Wang7d14d642019-01-23 15:10:08 -0800199 if not args[ "LOGCMD" ] is False:
Jon Hall3c0114c2020-08-11 15:07:42 -0700200 main.log.info( self.name + ": Executed :" + str( cmd ) +
You Wang7d14d642019-01-23 15:10:08 -0800201 " \t\t Expected Prompt '" + str( expectPrompt ) +
202 "' Found" )
adminbae64d82013-08-01 10:50:15 -0700203 elif index == 1:
204 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800205 self.handle.send( args[ "MORE" ] )
206 main.log.info(
207 "Found More screen to go , Sending a key to proceed" )
208 indexMore = self.handle.expect(
209 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700210 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800211 main.log.info(
212 "Found anoother More screen to go , Sending a key to proceed" )
213 self.handle.send( args[ "MORE" ] )
214 indexMore = self.handle.expect(
215 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700216 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800217 elif index == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700218 main.log.error( self.name + ": Command not found" )
adminbae64d82013-08-01 10:50:15 -0700219 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800220 elif index == 3:
Jon Hall3c0114c2020-08-11 15:07:42 -0700221 main.log.error( self.name + ": Expected Prompt not found, Time Out!!" )
kelvin8ec71442015-01-15 16:57:00 -0800222 main.log.error( expectPrompt )
Jon Hall3b489db2015-10-05 14:38:37 -0700223 self.LASTRSP = self.LASTRSP + self.handle.before
224 return self.LASTRSP
adminbae64d82013-08-01 10:50:15 -0700225 elif index == 4:
226 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800227 # self.handle.send( args[ "MORE" ] )
228 self.handle.sendcontrol( "D" )
229 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700230 "Found More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800231 indexMore = self.handle.expect(
232 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700233 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800234 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700235 "Found another More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800236 self.handle.sendcontrol( "D" )
237 indexMore = self.handle.expect(
238 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700239 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800240 main.last_response = self.remove_contol_chars( self.LASTRSP )
adminbae64d82013-08-01 10:50:15 -0700241 return self.LASTRSP
kelvin8ec71442015-01-15 16:57:00 -0800242
243 def remove_contol_chars( self, response ):
244 # RE_XML_ILLEGAL = '([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])|([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])'%( unichr( 0xd800 ),unichr( 0xdbff ),unichr( 0xdc00 ),unichr( 0xdfff ),unichr( 0xd800 ),unichr( 0xdbff ),unichr( 0xdc00 ),unichr( 0xdfff ),unichr( 0xd800 ),unichr( 0xdbff ),unichr( 0xdc00 ),unichr( 0xdfff ) )
245 # response = re.sub( RE_XML_ILLEGAL, "\n", response )
246 response = re.sub( r"[\x01-\x1F\x7F]", "", response )
247 # response = re.sub( r"\[\d+\;1H", "\n", response )
248 response = re.sub( r"\[\d+\;\d+H", "", response )
adminbae64d82013-08-01 10:50:15 -0700249 return response
adminbae64d82013-08-01 10:50:15 -0700250
kelvin8ec71442015-01-15 16:57:00 -0800251 def runAsSudoUser( self, handle, pwd, default ):
252
253 i = handle.expect( [ ".ssword:*", default, pexpect.EOF ] )
254 if i == 0:
255 handle.sendline( pwd )
Jon Hall5ec6b1b2015-09-17 18:20:14 -0700256 handle.sendline( "\n" )
kelvin8ec71442015-01-15 16:57:00 -0800257
258 if i == 1:
259 handle.expect( default )
260
261 if i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700262 main.log.error( self.name + ": Unable to run as Sudo user" )
kelvin8ec71442015-01-15 16:57:00 -0800263
adminbae64d82013-08-01 10:50:15 -0700264 return handle
adminbae64d82013-08-01 10:50:15 -0700265
kelvin8ec71442015-01-15 16:57:00 -0800266 def onfail( self ):
267 if 'onfail' in main.componentDictionary[ self.name ]:
268 commandList = main.componentDictionary[
269 self.name ][ 'onfail' ].split( "," )
270 for command in commandList:
271 response = self.execute(
272 cmd=command,
273 prompt="(.*)",
274 timeout=120 )
adminbae64d82013-08-01 10:50:15 -0700275
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700276 def secureCopy( self, userName, ipAddress, filePath, dstPath, pwd="",
277 direction="from" ):
kelvin8ec71442015-01-15 16:57:00 -0800278 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700279 Definition:
280 Execute scp command in linux to copy to/from a remote host
281 Required:
282 str userName - User name of the remote host
283 str ipAddress - IP address of the remote host
284 str filePath - File path including the file it self
285 str dstPath - Destination path
286 Optional:
287 str pwd - Password of the host
288 str direction - Direction of the scp, default to "from" which means
289 copy "from" the remote machine to local machine,
290 while "to" means copy "to" the remote machine from
291 local machine
kelvin8ec71442015-01-15 16:57:00 -0800292 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700293 returnVal = main.TRUE
adminbae64d82013-08-01 10:50:15 -0700294 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -0800295 refused = "ssh: connect to host " + \
Jon Hall547e0582015-09-21 17:35:40 -0700296 ipAddress + " port 22: Connection refused"
acsmars07f9d392015-07-15 10:30:58 -0700297
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700298 if direction == "from":
299 cmd = 'scp ' + str( userName ) + '@' + str( ipAddress ) + ':' + \
Jon Hall547e0582015-09-21 17:35:40 -0700300 str( filePath ) + ' ' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700301 elif direction == "to":
302 cmd = 'scp ' + str( filePath ) + ' ' + str( userName ) + \
Jon Hall547e0582015-09-21 17:35:40 -0700303 '@' + str( ipAddress ) + ':' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700304 else:
305 main.log.debug( "Wrong direction using secure copy command!" )
306 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800307
Jon Hall3c0114c2020-08-11 15:07:42 -0700308 main.log.info( self.name + ": Sending: " + cmd )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700309 self.handle.sendline( cmd )
Jon Hall547e0582015-09-21 17:35:40 -0700310 i = 0
311 while i < 2:
312 i = self.handle.expect( [
313 ssh_newkey,
314 'password:',
315 "100%",
316 refused,
317 "No such file or directory",
Jon Hall53c5e662016-04-13 16:06:56 -0700318 "Permission denied",
Devin Limdc78e202017-06-09 18:30:07 -0700319 self.prompt,
Jon Hall547e0582015-09-21 17:35:40 -0700320 pexpect.EOF,
321 pexpect.TIMEOUT ],
322 120 )
Jon Hall547e0582015-09-21 17:35:40 -0700323 if i == 0: # ask for ssh key confirmation
Jon Hall3c0114c2020-08-11 15:07:42 -0700324 main.log.info( self.name + ": ssh key confirmation received, sending yes" )
Jon Hall547e0582015-09-21 17:35:40 -0700325 self.handle.sendline( 'yes' )
326 elif i == 1: # Asked for ssh password
Jon Hall3c0114c2020-08-11 15:07:42 -0700327 main.log.info( self.name + ": ssh connection asked for password, gave password" )
Jon Hall547e0582015-09-21 17:35:40 -0700328 self.handle.sendline( pwd )
329 elif i == 2: # File finished transfering
Jon Hall3c0114c2020-08-11 15:07:42 -0700330 main.log.info( self.name + ": Secure copy successful" )
Jon Hall547e0582015-09-21 17:35:40 -0700331 returnVal = main.TRUE
332 elif i == 3: # Connection refused
333 main.log.error(
334 "ssh: connect to host " +
335 ipAddress +
336 " port 22: Connection refused" )
337 returnVal = main.FALSE
338 elif i == 4: # File Not found
Jon Hall3c0114c2020-08-11 15:07:42 -0700339 main.log.error( self.name + ": No such file found" )
Jon Hall547e0582015-09-21 17:35:40 -0700340 returnVal = main.FALSE
Jon Hall53c5e662016-04-13 16:06:56 -0700341 elif i == 5: # Permission denied
Jon Hall3c0114c2020-08-11 15:07:42 -0700342 main.log.error( self.name + ": Permission denied. Check folder permissions" )
Jon Hall53c5e662016-04-13 16:06:56 -0700343 returnVal = main.FALSE
344 elif i == 6: # prompt returned
Jon Hall4173b242017-09-12 17:04:38 -0700345 return returnVal
Jon Hall53c5e662016-04-13 16:06:56 -0700346 elif i == 7: # EOF
Jon Hall3c0114c2020-08-11 15:07:42 -0700347 main.log.error( self.name + ": Pexpect.EOF found!!!" )
Devin Lim44075962017-08-11 10:56:37 -0700348 main.cleanAndExit()
Jon Hall53c5e662016-04-13 16:06:56 -0700349 elif i == 8: # timeout
Jon Hall547e0582015-09-21 17:35:40 -0700350 main.log.error(
351 "No route to the Host " +
352 userName +
353 "@" +
354 ipAddress )
355 returnVal = main.FALSE
Devin Limdc78e202017-06-09 18:30:07 -0700356 self.handle.expect( self.prompt )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700357 return returnVal
358
359 def scp( self, remoteHost, filePath, dstPath, direction="from" ):
360 """
361 Definition:
362 Execute scp command in linux to copy to/from a remote host
363 Required:
364 * remoteHost - Test ON component to be parsed
365 str filePath - File path including the file it self
366 str dstPath - Destination path
367 Optional:
368 str direction - Direction of the scp, default to "from" which means
369 copy "from" the remote machine to local machine,
370 while "to" means copy "to" the remote machine from
371 local machine
372 """
373 return self.secureCopy( remoteHost.user_name,
374 remoteHost.ip_address,
375 filePath,
376 dstPath,
377 pwd=remoteHost.pwd,
378 direction=direction )
Devin Lim142b5342017-07-20 15:22:39 -0700379
380 def sshToNode( self, ipAddress, uName="sdn", pwd="rocks" ):
381 ssh_newkey = 'Are you sure you want to continue connecting'
382 refused = "ssh: connect to host " + ipAddress + " port 22: Connection refused"
383 handle = pexpect.spawn( 'ssh -X ' +
384 uName +
385 '@' +
386 ipAddress,
Jon Hall6c9e2da2018-11-06 12:01:23 -0800387 env={ "TERM": "vt100" },
Devin Lim142b5342017-07-20 15:22:39 -0700388 maxread=1000000,
389 timeout=60 )
390
391 # set tty window size
392 handle.setwinsize( 24, 250 )
393
394 i = 5
395 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -0700396 i = handle.expect( [ ssh_newkey,
397 'password:|Password:',
398 pexpect.EOF,
399 pexpect.TIMEOUT,
400 refused,
401 'teston>',
402 self.prompt ],
403 120 )
Devin Lim142b5342017-07-20 15:22:39 -0700404 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -0700405 main.log.info( self.name + ": ssh key confirmation received, send yes" )
Devin Lim142b5342017-07-20 15:22:39 -0700406 handle.sendline( 'yes' )
407 i = 5 # Run the loop again
408 continue
409 if i == 1: # Password required
410 if pwd:
411 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700412 "ssh connection asked for password, gave password" )
Devin Lim142b5342017-07-20 15:22:39 -0700413 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700414 main.log.info( self.name + ": Server asked for password, but none was "
Devin Lim142b5342017-07-20 15:22:39 -0700415 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700416 "no password." )
Devin Lim142b5342017-07-20 15:22:39 -0700417 pwd = ""
418 handle.sendline( pwd )
419 j = handle.expect( [ self.prompt,
420 'password:|Password:',
421 pexpect.EOF,
422 pexpect.TIMEOUT ],
423 120 )
424 if j != 0:
Jon Hall3c0114c2020-08-11 15:07:42 -0700425 main.log.error( self.name + ": Incorrect Password" )
Devin Lim44075962017-08-11 10:56:37 -0700426 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700427 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700428 main.log.error( self.name + ": Connection timeout" )
Devin Lim44075962017-08-11 10:56:37 -0700429 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700430 elif i == 3: # timeout
431 main.log.error(
432 "No route to the Host " +
433 uName +
434 "@" +
435 ipAddress )
Devin Lim44075962017-08-11 10:56:37 -0700436 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700437 elif i == 4:
438 main.log.error(
439 "ssh: connect to host " +
440 ipAddress +
441 " port 22: Connection refused" )
Devin Lim44075962017-08-11 10:56:37 -0700442 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700443 elif i == 6:
Jon Hall3c0114c2020-08-11 15:07:42 -0700444 main.log.info( self.name + ": Password not required logged in" )
Devin Lim142b5342017-07-20 15:22:39 -0700445
446 handle.sendline( "" )
447 handle.expect( self.prompt )
448 handle.sendline( "cd" )
449 handle.expect( self.prompt )
450
Jon Hall3c0114c2020-08-11 15:07:42 -0700451 main.log.info( self.name + ": Successfully ssh to " + ipAddress + "." )
Devin Lim142b5342017-07-20 15:22:39 -0700452 return handle
453
454 def exitFromSsh( self, handle, ipAddress ):
Devin Lim142b5342017-07-20 15:22:39 -0700455 try:
Jon Hall4f360bc2017-09-07 10:19:52 -0700456 handle.sendline( "logout" )
Devin Lim142b5342017-07-20 15:22:39 -0700457 handle.expect( "closed." )
Jon Hall3c0114c2020-08-11 15:07:42 -0700458 main.log.info( self.name + ": Successfully closed ssh connection from " + ipAddress )
Devin Lim142b5342017-07-20 15:22:39 -0700459 except pexpect.EOF:
Jon Hall3c0114c2020-08-11 15:07:42 -0700460 main.log.error( self.name + ": Failed to close the connection from " + ipAddress )
Jon Hall4f360bc2017-09-07 10:19:52 -0700461 try:
462 # check that this component handle still works
463 self.handle.sendline( "" )
464 self.handle.expect( self.prompt )
465 except pexpect.EOF:
466 main.log.error( self.handle.before )
Jon Hall3c0114c2020-08-11 15:07:42 -0700467 main.log.error( self.name + ": EOF after closing ssh connection" )
Jon Hall4173b242017-09-12 17:04:38 -0700468
469 def folderSize( self, path, size='10', unit='M', ignoreRoot=True ):
470 """
471 Run `du -h` on the folder path and verifies the folder(s) size is
472 less than the given size. Note that if multiple subdirectories are
473 present, the result will be the OR of all the individual subdirectories.
474
475 Arguments:
476 path - A string containing the path supplied to the du command
477 size - The number portion of the file size that the results will be compared to
478 unit - The unit portion of the file size that the results will be compared to
479 ignoreRoot - If True, will ignore the "root" of the path supplied to du. I.E. will ignore `.`
480
481 Returns True if the folder(s) size(s) are less than SIZE UNITS, else returns False
482 """
483 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
484 unitsList = [ 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]
485 try:
486 # make sure we convert units if size is too big
487 size = float( size )
488 if size >= 1000:
489 size = size / 1000
490 unit = unitsList[ unitsList.index( unit + 1 ) ]
491 cmdStr = "du -h " + path
492 self.handle.sendline( cmdStr )
493 self.handle.expect( self.prompt )
494 output = self.handle.before
495 assert "cannot access" not in output
496 assert "command not found" not in output
497 main.log.debug( output )
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700498 lines = [ line for line in output.split( '\r\n' ) ]
Jon Hall4173b242017-09-12 17:04:38 -0700499 retValue = True
500 if ignoreRoot:
501 lastIndex = -2
502 else:
503 lastIndex = -1
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700504 for line in lines[ 1:lastIndex ]:
Jon Hall4173b242017-09-12 17:04:38 -0700505 parsed = line.split()
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700506 sizeMatch = parsed[ 0 ]
507 folder = parsed[ 1 ]
Jon Hall4173b242017-09-12 17:04:38 -0700508 match = re.search( sizeRe, sizeMatch )
509 num = match.group( 'number' )
510 unitMatch = match.group( 'unit' )
511 if unitsList.index( unitMatch ) < unitsList.index( unit ):
512 retValue &= True
513 elif unitsList.index( unitMatch ) == unitsList.index( unit ):
514 if float( num ) < float( size ):
515 retValue &= True
516 else:
517 retValue &= False
518 elif unitsList.index( unitMatch ) > unitsList.index( unit ):
519 retValue &= False
520 return retValue
521 except AssertionError:
522 main.log.error( self.name + ": Could not execute command: " + output )
523 return False
Jon Hall43060f62020-06-23 13:13:33 -0700524 except ValueError as e:
525 main.log.error( self.name + ": Error parsing output: " + output )
526 main.log.error( e )
527 return False
Jon Hall4173b242017-09-12 17:04:38 -0700528 except pexpect.TIMEOUT:
529 main.log.exception( self.name + ": TIMEOUT exception found" )
530 main.log.error( self.name + ": " + self.handle.before )
531 return False
532 except pexpect.EOF:
533 main.log.error( self.name + ": EOF exception found" )
534 main.log.error( self.name + ": " + self.handle.before )
535 main.cleanAndExit()
Jon Hall0e240372018-05-02 11:21:57 -0700536
537 def setEnv( self, variable, value=None ):
538 """
539 Sets the environment variable to the given value for the current shell session.
540 If value is None, will unset the variable.
541
542 Required Arguments:
543 variable - The name of the environment variable to set.
544
545 Optional Arguments:
546 value - The value to set the variable to. ( Defaults to None, which unsets the variable )
547
548 Returns True if no errors are detected else returns False
549 """
550 try:
551 if value:
552 cmd = "export {}={}".format( variable, value )
553 else:
554 cmd = "unset {}".format( variable )
555 self.handle.sendline( cmd )
556 self.handle.expect( self.prompt )
Jon Hall3c0114c2020-08-11 15:07:42 -0700557 output = self.handle.before
558 main.log.debug( output )
Jon Hall0e240372018-05-02 11:21:57 -0700559 return True
560 except AssertionError:
561 main.log.error( self.name + ": Could not execute command: " + output )
562 return False
563 except pexpect.TIMEOUT:
564 main.log.exception( self.name + ": TIMEOUT exception found" )
565 main.log.error( self.name + ": " + self.handle.before )
566 return False
567 except pexpect.EOF:
568 main.log.error( self.name + ": EOF exception found" )
569 main.log.error( self.name + ": " + self.handle.before )
570 main.cleanAndExit()
You Wangb65d2372018-08-17 15:37:59 -0700571
572 def exitFromCmd( self, expect, retry=10 ):
573 """
574 Call this function when sending ctrl+c is required to kill the current
575 command. It will retry multiple times until the running command is
576 completely killed and expected string is returned from the handle.
577 Required:
You Wangd4fae5c2018-08-22 13:56:49 -0700578 expect: expected string or list of strings which indicates that the
579 previous command was killed successfully.
You Wangb65d2372018-08-17 15:37:59 -0700580 Optional:
581 retry: maximum number of ctrl+c that will be sent.
582 """
You Wangd4fae5c2018-08-22 13:56:49 -0700583 expect = [ expect ] if isinstance( expect, str ) else expect
You Wangb65d2372018-08-17 15:37:59 -0700584 try:
585 while retry >= 0:
586 main.log.debug( self.name + ": sending ctrl+c to kill the command" )
587 self.handle.send( "\x03" )
You Wangd4fae5c2018-08-22 13:56:49 -0700588 i = self.handle.expect( expect + [ pexpect.TIMEOUT ], timeout=3 )
You Wangb65d2372018-08-17 15:37:59 -0700589 main.log.debug( self.handle.before )
You Wangd4fae5c2018-08-22 13:56:49 -0700590 if i < len( expect ):
You Wangb65d2372018-08-17 15:37:59 -0700591 main.log.debug( self.name + ": successfully killed the command" )
592 return main.TRUE
593 retry -= 1
594 main.log.warn( self.name + ": failed to kill the command" )
595 return main.FALSE
596 except pexpect.EOF:
597 main.log.error( self.name + ": EOF exception found" )
598 main.log.error( self.name + ": " + self.handle.before )
599 return main.FALSE
600 except Exception:
601 main.log.exception( self.name + ": Uncaught exception!" )
602 return main.FALSE
Jon Hall43060f62020-06-23 13:13:33 -0700603
604 def cleanOutput( self, output, debug=False ):
605 """
606 Clean ANSI characters from output
607 """
608 ansiEscape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
609 cleaned = ansiEscape.sub( '', output )
610 if debug:
611 main.log.debug( self.name + ": cleanOutput:" )
612 main.log.debug( self.name + ": " + repr( cleaned ) )
613 return cleaned
Jon Hall3c0114c2020-08-11 15:07:42 -0700614
615 def dockerPull( self, image, tag=None ):
616 """
617 Pull a docker image from a registry
618 """
619 try:
620 imgStr = "%s%s" % ( image, ":%s" % tag if tag else "" )
621 cmdStr = "docker pull %s" % imgStr
622 main.log.info( self.name + ": sending: " + cmdStr )
623 self.handle.sendline( cmdStr)
624 i = self.handle.expect( [ self.prompt,
625 "Error response from daemon",
626 pexpect.TIMEOUT ], 120 )
627 if i == 0:
628 return main.TRUE
629 else:
630 main.log.error( self.name + ": Error pulling docker image " + imgStr )
631 output = self.handle.before + str( self.handle.after )
632 if i == 1:
633 self.handle.expect( self.prompt )
634 output += self.handle.before + str( self.handle.after )
635 main.log.debug( self.name + ": " + output )
636 return main.FALSE
637 except pexpect.EOF:
638 main.log.error( self.name + ": EOF exception found" )
639 main.log.error( self.name + ": " + self.handle.before )
640 return main.FALSE
641 except Exception:
642 main.log.exception( self.name + ": Uncaught exception!" )
643 return main.FALSE
644
645 def dockerBuild( self, path, imageTag, pull=False, options="", timeout=600 ):
646 """
647 Build a docker image
648 Required Arguments:
649 - path: Path to the dockerfile, it is recommended to avoid relative paths
650 - imageTag: Give a tag to the built docker image
651 Optional Arguments:
652 - pull: Whether to attempt to pull latest images before building
653 - options: A string containing any addition optional arguments
654 for the docker build command
655 - timeout: How many seconds to wait for the build to complete
656 """
657 try:
658 response = main.TRUE
659 if pull:
660 options = "--pull " + options
661 cmdStr = "docker build -t %s %s %s" % ( imageTag, options, path )
662 main.log.info( self.name + ": sending: " + cmdStr )
663 self.handle.sendline( cmdStr)
664 i = self.handle.expect( [ "Successfully built",
665 "Error response from daemon",
666 pexpect.TIMEOUT ], timeout=timeout )
667 output = self.handle.before
668 if i == 0:
669 output += self.handle.after
670 self.handle.expect( self.prompt )
671 output += self.handle.before + self.handle.after
672 return response
673 elif i == 1:
674 response = main.FALSE
675 output += self.handle.after
676 self.handle.expect( self.prompt )
677 output += self.handle.before + self.handle.after
678 elif i == 2:
679 response = main.FALSE
680 main.log.error( self.name + ": Error building docker image" )
681 main.log.debug( self.name + ": " + output )
682 return response
683 except pexpect.EOF:
684 main.log.error( self.name + ": EOF exception found" )
685 main.log.error( self.name + ": " + self.handle.before )
686 return main.FALSE
687 except Exception:
688 main.log.exception( self.name + ": Uncaught exception!" )
689 return main.FALSE
690
691 def dockerStop( self, containerName ):
692 """
693 Stop a docker container
694 Required Arguments:
695 - containerName: Name of the container to stop
696 """
697 try:
698 cmdStr = "docker stop %s" % ( containerName )
699 main.log.info( self.name + ": sending: " + cmdStr )
700 self.handle.sendline( cmdStr)
701 i = self.handle.expect( [ self.prompt,
702 "Error response from daemon",
703 pexpect.TIMEOUT ], 120 )
704 output = self.handle.before
705 if i == 0:
706 return main.TRUE
707 elif i == 1:
708 output += self.handle.after
709 self.handle.expect( self.prompt )
710 output += self.handle.before
711 elif i == 2:
712 pass
713 main.log.debug( "%s: %s" % ( self.name, output ) )
714 if "No such container" in output:
715 return main.TRUE
716 main.log.error( self.name + ": Error stopping docker image" )
717 main.log.debug( self.name + ": " + output )
718 return main.FALSE
719 except pexpect.EOF:
720 main.log.error( self.name + ": EOF exception found" )
721 main.log.error( self.name + ": " + self.handle.before )
722 return main.FALSE
723 except Exception:
724 main.log.exception( self.name + ": Uncaught exception!" )
725 return main.FALSE
726
727 def dockerRun( self, image, containerName, options="", imageArgs="" ):
728 """
729 Run a docker image
730 Required Arguments:
731 - containerName: Give a name to the container once its started
732 - image: Run the given image
733 Optional Arguments:
734 - options: A string containing any addition optional arguments
735 for the docker run command
736 - imageArgs: A string containing command line arguments for the
737 command run by docker
738 """
739 try:
740 cmdStr = "docker run --name %s %s %s %s" % ( containerName,
741 options if options else "",
742 image,
743 imageArgs )
744 main.log.info( self.name + ": sending: " + cmdStr )
745 self.handle.sendline( cmdStr)
746 i = self.handle.expect( [ self.prompt,
747 "Error response from daemon",
748 pexpect.TIMEOUT ], 120 )
749 if i == 0:
750 return main.TRUE
751 else:
752 output = self.handle.before
753 main.log.debug( self.name + ": " + output )
754 main.log.error( self.name + ": Error running docker image" )
755 if i == 1:
756 output += self.handle.after
757 self.handle.expect( self.prompt )
758 output += self.handle.before + self.handle.after
759 main.log.debug( self.name + ": " + output )
760 return main.FALSE
761 except pexpect.EOF:
762 main.log.error( self.name + ": EOF exception found" )
763 main.log.error( self.name + ": " + self.handle.before )
764 return main.FALSE
765 except Exception:
766 main.log.exception( self.name + ": Uncaught exception!" )
767 return main.FALSE
768
769 def dockerAttach( self, containerName, dockerPrompt="" ):
770 """
771 Attach to a docker image
772 Required Arguments:
773 - containerName: The name of the container to attach to
774 Optional Arguments:
775 - dockerPrompt: a regex for matching the docker shell prompt
776 """
777 try:
778 if dockerPrompt:
779 self.dockerPrompt = dockerPrompt
780 cmdStr = "docker attach %s" % containerName
781 main.log.info( self.name + ": sending: " + cmdStr )
782 self.handle.sendline( cmdStr)
783 i = self.handle.expect( [ self.dockerPrompt,
784 "Error response from daemon",
785 pexpect.TIMEOUT ] )
786 if i == 0:
787 self.inDocker = True
788 return main.TRUE
789 else:
790 main.log.error( self.name + ": Error connecting to docker container" )
791 output = self.handle.before + str( self.handle.after )
792 if i == 1:
793 self.handle.expect( self.prompt )
794 output += self.handle.before + str( self.handle.after )
795 main.log.debug( self.name + ": " + output )
796 return main.FALSE
797 except pexpect.EOF:
798 main.log.error( self.name + ": EOF exception found" )
799 main.log.error( self.name + ": " + self.handle.before )
800 return main.FALSE
801 except AttributeError as e:
802 main.log.exception( self.name + ": AttributeError - " + str( e ) )
803 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
804 main.cleanup()
805 main.exit()
806 except Exception:
807 main.log.exception( self.name + ": Uncaught exception!" )
808 return main.FALSE
809
810 def dockerExec( self, containerName, command="/bin/bash", options="-it", dockerPrompt="" ):
811 """
812 Attach to a docker image
813 Required Arguments:
814 - containerName: The name of the container to attach to
815 Optional Arguments:
816 - command: Command to run in the docker container
817 - options: Docker exec options
818 - dockerPrompt: a regex for matching the docker shell prompt
819 """
820 try:
821 if dockerPrompt:
822 self.dockerPrompt = dockerPrompt
823 cmdStr = "docker exec %s %s %s" % ( options, containerName, command )
824 main.log.info( self.name + ": sending: " + cmdStr )
825 self.handle.sendline( cmdStr)
826 i = self.handle.expect( [ self.dockerPrompt,
827 "Error response from daemon",
828 pexpect.TIMEOUT ] )
829 if i == 0:
830 self.inDocker = True
831 return main.TRUE
832 else:
833 main.log.error( self.name + ": Error connecting to docker container" )
834 output = self.handle.before + str( self.handle.after )
835 if i == 1:
836 self.handle.expect( self.prompt )
837 output += self.handle.before + str( self.handle.after )
838 main.log.debug( self.name + ": " + output )
839 return main.FALSE
840 except pexpect.EOF:
841 main.log.error( self.name + ": EOF exception found" )
842 main.log.error( self.name + ": " + self.handle.before )
843 return main.FALSE
844 except AttributeError as e:
845 main.log.exception( self.name + ": AttributeError - " + str( e ) )
846 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
847 main.cleanup()
848 main.exit()
849 except Exception:
850 main.log.exception( self.name + ": Uncaught exception!" )
851 return main.FALSE
852
853 def dockerCp( self, containerName, dockerPath, hostPath, direction="from" ):
854 """
855 Copy a file from/to a docker container to the host
856 Required Arguments:
857 - containerName: The name of the container to copy from/to
858 - dockerPath: the path in the container to copy from/to
859 - hostPath: the path on the host to copy to/from
860 Optional Arguments:
861 - direction: Choose whether to copy "from" the container or "to" the container
862 """
863 try:
864 cmdStr = "docker cp "
865 if direction == "from":
866 cmdStr += "%s:%s %s" % ( containerName, dockerPath, hostPath )
867 elif direction == "to":
868 cmdStr += "%s %s:%s" % ( hostPath, containerName, dockerPath )
869 main.log.info( self.name + ": sending: " + cmdStr )
870 self.handle.sendline( cmdStr)
871 i = self.handle.expect( [ self.prompt,
872 "Error",
873 pexpect.TIMEOUT ] )
874 if i == 0:
875 retValue = main.TRUE
876 else:
877 main.log.error( self.name + ": Error in docker cp" )
878 output = self.handle.before + str( self.handle.after )
879 if i == 1:
880 self.handle.expect( self.prompt )
881 output += self.handle.before + str( self.handle.after )
882 main.log.debug( self.name + ": " + output )
883 retValue = main.FALSE
884 return retValue
885 except pexpect.EOF:
886 main.log.error( self.name + ": EOF exception found" )
887 main.log.error( self.name + ": " + self.handle.before )
888 return main.FALSE
889 except AttributeError as e:
890 main.log.exception( self.name + ": AttributeError - " + str( e ) )
891 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
892 main.cleanup()
893 main.exit()
894 except Exception:
895 main.log.exception( self.name + ": Uncaught exception!" )
896 return main.FALSE
897
898 def dockerDisconnect( self ):
899 """
900 Send ctrl-c, ctrl-d to session, which should close and exit the
901 attached docker session. This will likely exit the running program
902 in the container and also stop the container.
903 """
904 try:
905 cmdStr = "\x03"
906 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
907 self.handle.send( cmdStr)
908 cmdStr = "\x04"
909 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
910 self.handle.send( cmdStr)
911 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
912 if i == 0:
913 self.inDocker = False
914 return main.TRUE
915 else:
916 main.log.error( self.name + ": Error disconnecting from docker image" )
917 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
918 return main.FALSE
919 except pexpect.EOF:
920 main.log.error( self.name + ": EOF exception found" )
921 main.log.error( self.name + ": " + self.handle.before )
922 return main.FALSE
923 except Exception:
924 main.log.exception( self.name + ": Uncaught exception!" )
925 return main.FALSE