blob: 3083f44bd773dee4ac635ba5b21d8016a191e5d9 [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="",
Jon Hall39570262020-11-17 12:18:19 -0800277 direction="from", options="" ):
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"
Jon Hall39570262020-11-17 12:18:19 -0800297 cmd = "scp %s " % options
acsmars07f9d392015-07-15 10:30:58 -0700298
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700299 if direction == "from":
Jon Hall39570262020-11-17 12:18:19 -0800300 cmd = cmd + str( userName ) + '@' + str( ipAddress ) + ':' + \
Jon Hall547e0582015-09-21 17:35:40 -0700301 str( filePath ) + ' ' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700302 elif direction == "to":
Jon Hall39570262020-11-17 12:18:19 -0800303 cmd = cmd + str( filePath ) + ' ' + str( userName ) + \
Jon Hall547e0582015-09-21 17:35:40 -0700304 '@' + str( ipAddress ) + ':' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700305 else:
306 main.log.debug( "Wrong direction using secure copy command!" )
307 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800308
Jon Hall3c0114c2020-08-11 15:07:42 -0700309 main.log.info( self.name + ": Sending: " + cmd )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700310 self.handle.sendline( cmd )
Jon Hall547e0582015-09-21 17:35:40 -0700311 i = 0
312 while i < 2:
313 i = self.handle.expect( [
314 ssh_newkey,
315 'password:',
316 "100%",
317 refused,
318 "No such file or directory",
Jon Hall53c5e662016-04-13 16:06:56 -0700319 "Permission denied",
Devin Limdc78e202017-06-09 18:30:07 -0700320 self.prompt,
Jon Hall547e0582015-09-21 17:35:40 -0700321 pexpect.EOF,
322 pexpect.TIMEOUT ],
323 120 )
Jon Hall547e0582015-09-21 17:35:40 -0700324 if i == 0: # ask for ssh key confirmation
Jon Hall3c0114c2020-08-11 15:07:42 -0700325 main.log.info( self.name + ": ssh key confirmation received, sending yes" )
Jon Hall547e0582015-09-21 17:35:40 -0700326 self.handle.sendline( 'yes' )
327 elif i == 1: # Asked for ssh password
Jon Hall3c0114c2020-08-11 15:07:42 -0700328 main.log.info( self.name + ": ssh connection asked for password, gave password" )
Jon Hall547e0582015-09-21 17:35:40 -0700329 self.handle.sendline( pwd )
330 elif i == 2: # File finished transfering
Jon Hall3c0114c2020-08-11 15:07:42 -0700331 main.log.info( self.name + ": Secure copy successful" )
Jon Hall547e0582015-09-21 17:35:40 -0700332 returnVal = main.TRUE
333 elif i == 3: # Connection refused
334 main.log.error(
335 "ssh: connect to host " +
336 ipAddress +
337 " port 22: Connection refused" )
338 returnVal = main.FALSE
339 elif i == 4: # File Not found
Jon Hall3c0114c2020-08-11 15:07:42 -0700340 main.log.error( self.name + ": No such file found" )
Jon Hall39570262020-11-17 12:18:19 -0800341 main.log.debug( self.handle.before + self.handle.after )
Jon Hall547e0582015-09-21 17:35:40 -0700342 returnVal = main.FALSE
Jon Hall53c5e662016-04-13 16:06:56 -0700343 elif i == 5: # Permission denied
Jon Hall3c0114c2020-08-11 15:07:42 -0700344 main.log.error( self.name + ": Permission denied. Check folder permissions" )
Jon Hall39570262020-11-17 12:18:19 -0800345 main.log.debug( self.handle.before + self.handle.after )
Jon Hall53c5e662016-04-13 16:06:56 -0700346 returnVal = main.FALSE
347 elif i == 6: # prompt returned
Jon Hall4173b242017-09-12 17:04:38 -0700348 return returnVal
Jon Hall53c5e662016-04-13 16:06:56 -0700349 elif i == 7: # EOF
Jon Hall3c0114c2020-08-11 15:07:42 -0700350 main.log.error( self.name + ": Pexpect.EOF found!!!" )
Devin Lim44075962017-08-11 10:56:37 -0700351 main.cleanAndExit()
Jon Hall53c5e662016-04-13 16:06:56 -0700352 elif i == 8: # timeout
Jon Hall547e0582015-09-21 17:35:40 -0700353 main.log.error(
354 "No route to the Host " +
355 userName +
356 "@" +
357 ipAddress )
358 returnVal = main.FALSE
Devin Limdc78e202017-06-09 18:30:07 -0700359 self.handle.expect( self.prompt )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700360 return returnVal
361
Jon Hall39570262020-11-17 12:18:19 -0800362 def scp( self, remoteHost, filePath, dstPath, direction="from", options="" ):
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700363 """
364 Definition:
365 Execute scp command in linux to copy to/from a remote host
366 Required:
367 * remoteHost - Test ON component to be parsed
368 str filePath - File path including the file it self
369 str dstPath - Destination path
370 Optional:
371 str direction - Direction of the scp, default to "from" which means
372 copy "from" the remote machine to local machine,
373 while "to" means copy "to" the remote machine from
374 local machine
375 """
376 return self.secureCopy( remoteHost.user_name,
377 remoteHost.ip_address,
378 filePath,
379 dstPath,
380 pwd=remoteHost.pwd,
Jon Hall39570262020-11-17 12:18:19 -0800381 direction=direction,
382 options=options )
Devin Lim142b5342017-07-20 15:22:39 -0700383
384 def sshToNode( self, ipAddress, uName="sdn", pwd="rocks" ):
385 ssh_newkey = 'Are you sure you want to continue connecting'
386 refused = "ssh: connect to host " + ipAddress + " port 22: Connection refused"
387 handle = pexpect.spawn( 'ssh -X ' +
388 uName +
389 '@' +
390 ipAddress,
Jon Hall6c9e2da2018-11-06 12:01:23 -0800391 env={ "TERM": "vt100" },
Devin Lim142b5342017-07-20 15:22:39 -0700392 maxread=1000000,
393 timeout=60 )
394
395 # set tty window size
396 handle.setwinsize( 24, 250 )
397
398 i = 5
399 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -0700400 i = handle.expect( [ ssh_newkey,
401 'password:|Password:',
402 pexpect.EOF,
403 pexpect.TIMEOUT,
404 refused,
405 'teston>',
406 self.prompt ],
407 120 )
Devin Lim142b5342017-07-20 15:22:39 -0700408 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -0700409 main.log.info( self.name + ": ssh key confirmation received, send yes" )
Devin Lim142b5342017-07-20 15:22:39 -0700410 handle.sendline( 'yes' )
411 i = 5 # Run the loop again
412 continue
413 if i == 1: # Password required
414 if pwd:
415 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700416 "ssh connection asked for password, gave password" )
Devin Lim142b5342017-07-20 15:22:39 -0700417 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700418 main.log.info( self.name + ": Server asked for password, but none was "
Devin Lim142b5342017-07-20 15:22:39 -0700419 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700420 "no password." )
Devin Lim142b5342017-07-20 15:22:39 -0700421 pwd = ""
422 handle.sendline( pwd )
423 j = handle.expect( [ self.prompt,
424 'password:|Password:',
425 pexpect.EOF,
426 pexpect.TIMEOUT ],
427 120 )
428 if j != 0:
Jon Hall3c0114c2020-08-11 15:07:42 -0700429 main.log.error( self.name + ": Incorrect Password" )
Devin Lim44075962017-08-11 10:56:37 -0700430 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700431 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700432 main.log.error( self.name + ": Connection timeout" )
Devin Lim44075962017-08-11 10:56:37 -0700433 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700434 elif i == 3: # timeout
435 main.log.error(
436 "No route to the Host " +
437 uName +
438 "@" +
439 ipAddress )
Devin Lim44075962017-08-11 10:56:37 -0700440 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700441 elif i == 4:
442 main.log.error(
443 "ssh: connect to host " +
444 ipAddress +
445 " port 22: Connection refused" )
Devin Lim44075962017-08-11 10:56:37 -0700446 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700447 elif i == 6:
Jon Hall3c0114c2020-08-11 15:07:42 -0700448 main.log.info( self.name + ": Password not required logged in" )
Devin Lim142b5342017-07-20 15:22:39 -0700449
450 handle.sendline( "" )
451 handle.expect( self.prompt )
452 handle.sendline( "cd" )
453 handle.expect( self.prompt )
454
Jon Hall3c0114c2020-08-11 15:07:42 -0700455 main.log.info( self.name + ": Successfully ssh to " + ipAddress + "." )
Devin Lim142b5342017-07-20 15:22:39 -0700456 return handle
457
458 def exitFromSsh( self, handle, ipAddress ):
Devin Lim142b5342017-07-20 15:22:39 -0700459 try:
Jon Hall4f360bc2017-09-07 10:19:52 -0700460 handle.sendline( "logout" )
Devin Lim142b5342017-07-20 15:22:39 -0700461 handle.expect( "closed." )
Jon Hall3c0114c2020-08-11 15:07:42 -0700462 main.log.info( self.name + ": Successfully closed ssh connection from " + ipAddress )
Devin Lim142b5342017-07-20 15:22:39 -0700463 except pexpect.EOF:
Jon Hall3c0114c2020-08-11 15:07:42 -0700464 main.log.error( self.name + ": Failed to close the connection from " + ipAddress )
Jon Hall4f360bc2017-09-07 10:19:52 -0700465 try:
466 # check that this component handle still works
467 self.handle.sendline( "" )
468 self.handle.expect( self.prompt )
469 except pexpect.EOF:
470 main.log.error( self.handle.before )
Jon Hall3c0114c2020-08-11 15:07:42 -0700471 main.log.error( self.name + ": EOF after closing ssh connection" )
Jon Hall4173b242017-09-12 17:04:38 -0700472
473 def folderSize( self, path, size='10', unit='M', ignoreRoot=True ):
474 """
475 Run `du -h` on the folder path and verifies the folder(s) size is
476 less than the given size. Note that if multiple subdirectories are
477 present, the result will be the OR of all the individual subdirectories.
478
479 Arguments:
480 path - A string containing the path supplied to the du command
481 size - The number portion of the file size that the results will be compared to
482 unit - The unit portion of the file size that the results will be compared to
483 ignoreRoot - If True, will ignore the "root" of the path supplied to du. I.E. will ignore `.`
484
485 Returns True if the folder(s) size(s) are less than SIZE UNITS, else returns False
486 """
487 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
488 unitsList = [ 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]
489 try:
490 # make sure we convert units if size is too big
491 size = float( size )
492 if size >= 1000:
493 size = size / 1000
494 unit = unitsList[ unitsList.index( unit + 1 ) ]
495 cmdStr = "du -h " + path
496 self.handle.sendline( cmdStr )
497 self.handle.expect( self.prompt )
498 output = self.handle.before
499 assert "cannot access" not in output
500 assert "command not found" not in output
501 main.log.debug( output )
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700502 lines = [ line for line in output.split( '\r\n' ) ]
Jon Hall4173b242017-09-12 17:04:38 -0700503 retValue = True
504 if ignoreRoot:
505 lastIndex = -2
506 else:
507 lastIndex = -1
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700508 for line in lines[ 1:lastIndex ]:
Jon Hall4173b242017-09-12 17:04:38 -0700509 parsed = line.split()
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700510 sizeMatch = parsed[ 0 ]
511 folder = parsed[ 1 ]
Jon Hall4173b242017-09-12 17:04:38 -0700512 match = re.search( sizeRe, sizeMatch )
513 num = match.group( 'number' )
514 unitMatch = match.group( 'unit' )
515 if unitsList.index( unitMatch ) < unitsList.index( unit ):
516 retValue &= True
517 elif unitsList.index( unitMatch ) == unitsList.index( unit ):
518 if float( num ) < float( size ):
519 retValue &= True
520 else:
521 retValue &= False
522 elif unitsList.index( unitMatch ) > unitsList.index( unit ):
523 retValue &= False
524 return retValue
525 except AssertionError:
526 main.log.error( self.name + ": Could not execute command: " + output )
527 return False
Jon Hall43060f62020-06-23 13:13:33 -0700528 except ValueError as e:
529 main.log.error( self.name + ": Error parsing output: " + output )
530 main.log.error( e )
531 return False
Jon Hall4173b242017-09-12 17:04:38 -0700532 except pexpect.TIMEOUT:
533 main.log.exception( self.name + ": TIMEOUT exception found" )
534 main.log.error( self.name + ": " + self.handle.before )
535 return False
536 except pexpect.EOF:
537 main.log.error( self.name + ": EOF exception found" )
538 main.log.error( self.name + ": " + self.handle.before )
539 main.cleanAndExit()
Jon Hall0e240372018-05-02 11:21:57 -0700540
541 def setEnv( self, variable, value=None ):
542 """
543 Sets the environment variable to the given value for the current shell session.
544 If value is None, will unset the variable.
545
546 Required Arguments:
547 variable - The name of the environment variable to set.
548
549 Optional Arguments:
550 value - The value to set the variable to. ( Defaults to None, which unsets the variable )
551
552 Returns True if no errors are detected else returns False
553 """
554 try:
555 if value:
556 cmd = "export {}={}".format( variable, value )
557 else:
558 cmd = "unset {}".format( variable )
559 self.handle.sendline( cmd )
560 self.handle.expect( self.prompt )
Jon Hall3c0114c2020-08-11 15:07:42 -0700561 output = self.handle.before
562 main.log.debug( output )
Jon Hall0e240372018-05-02 11:21:57 -0700563 return True
564 except AssertionError:
565 main.log.error( self.name + ": Could not execute command: " + output )
566 return False
567 except pexpect.TIMEOUT:
568 main.log.exception( self.name + ": TIMEOUT exception found" )
569 main.log.error( self.name + ": " + self.handle.before )
570 return False
571 except pexpect.EOF:
572 main.log.error( self.name + ": EOF exception found" )
573 main.log.error( self.name + ": " + self.handle.before )
574 main.cleanAndExit()
You Wangb65d2372018-08-17 15:37:59 -0700575
576 def exitFromCmd( self, expect, retry=10 ):
577 """
578 Call this function when sending ctrl+c is required to kill the current
579 command. It will retry multiple times until the running command is
580 completely killed and expected string is returned from the handle.
581 Required:
You Wangd4fae5c2018-08-22 13:56:49 -0700582 expect: expected string or list of strings which indicates that the
583 previous command was killed successfully.
You Wangb65d2372018-08-17 15:37:59 -0700584 Optional:
585 retry: maximum number of ctrl+c that will be sent.
586 """
You Wangd4fae5c2018-08-22 13:56:49 -0700587 expect = [ expect ] if isinstance( expect, str ) else expect
You Wangb65d2372018-08-17 15:37:59 -0700588 try:
589 while retry >= 0:
590 main.log.debug( self.name + ": sending ctrl+c to kill the command" )
591 self.handle.send( "\x03" )
You Wangd4fae5c2018-08-22 13:56:49 -0700592 i = self.handle.expect( expect + [ pexpect.TIMEOUT ], timeout=3 )
You Wangb65d2372018-08-17 15:37:59 -0700593 main.log.debug( self.handle.before )
You Wangd4fae5c2018-08-22 13:56:49 -0700594 if i < len( expect ):
You Wangb65d2372018-08-17 15:37:59 -0700595 main.log.debug( self.name + ": successfully killed the command" )
596 return main.TRUE
597 retry -= 1
598 main.log.warn( self.name + ": failed to kill the command" )
599 return main.FALSE
600 except pexpect.EOF:
601 main.log.error( self.name + ": EOF exception found" )
602 main.log.error( self.name + ": " + self.handle.before )
603 return main.FALSE
604 except Exception:
605 main.log.exception( self.name + ": Uncaught exception!" )
606 return main.FALSE
Jon Hall43060f62020-06-23 13:13:33 -0700607
608 def cleanOutput( self, output, debug=False ):
609 """
610 Clean ANSI characters from output
611 """
612 ansiEscape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
613 cleaned = ansiEscape.sub( '', output )
614 if debug:
615 main.log.debug( self.name + ": cleanOutput:" )
616 main.log.debug( self.name + ": " + repr( cleaned ) )
617 return cleaned
Jon Hall3c0114c2020-08-11 15:07:42 -0700618
619 def dockerPull( self, image, tag=None ):
620 """
621 Pull a docker image from a registry
622 """
623 try:
624 imgStr = "%s%s" % ( image, ":%s" % tag if tag else "" )
625 cmdStr = "docker pull %s" % imgStr
626 main.log.info( self.name + ": sending: " + cmdStr )
627 self.handle.sendline( cmdStr)
628 i = self.handle.expect( [ self.prompt,
629 "Error response from daemon",
630 pexpect.TIMEOUT ], 120 )
631 if i == 0:
632 return main.TRUE
633 else:
634 main.log.error( self.name + ": Error pulling docker image " + imgStr )
635 output = self.handle.before + str( self.handle.after )
636 if i == 1:
637 self.handle.expect( self.prompt )
638 output += self.handle.before + str( self.handle.after )
639 main.log.debug( self.name + ": " + output )
640 return main.FALSE
641 except pexpect.EOF:
642 main.log.error( self.name + ": EOF exception found" )
643 main.log.error( self.name + ": " + self.handle.before )
644 return main.FALSE
645 except Exception:
646 main.log.exception( self.name + ": Uncaught exception!" )
647 return main.FALSE
648
649 def dockerBuild( self, path, imageTag, pull=False, options="", timeout=600 ):
650 """
651 Build a docker image
652 Required Arguments:
653 - path: Path to the dockerfile, it is recommended to avoid relative paths
654 - imageTag: Give a tag to the built docker image
655 Optional Arguments:
656 - pull: Whether to attempt to pull latest images before building
657 - options: A string containing any addition optional arguments
658 for the docker build command
659 - timeout: How many seconds to wait for the build to complete
660 """
661 try:
662 response = main.TRUE
663 if pull:
664 options = "--pull " + options
665 cmdStr = "docker build -t %s %s %s" % ( imageTag, options, path )
666 main.log.info( self.name + ": sending: " + cmdStr )
667 self.handle.sendline( cmdStr)
668 i = self.handle.expect( [ "Successfully built",
669 "Error response from daemon",
670 pexpect.TIMEOUT ], timeout=timeout )
671 output = self.handle.before
672 if i == 0:
673 output += self.handle.after
674 self.handle.expect( self.prompt )
675 output += self.handle.before + self.handle.after
676 return response
677 elif i == 1:
678 response = main.FALSE
679 output += self.handle.after
680 self.handle.expect( self.prompt )
681 output += self.handle.before + self.handle.after
682 elif i == 2:
683 response = main.FALSE
684 main.log.error( self.name + ": Error building docker image" )
685 main.log.debug( self.name + ": " + output )
686 return response
687 except pexpect.EOF:
688 main.log.error( self.name + ": EOF exception found" )
689 main.log.error( self.name + ": " + self.handle.before )
690 return main.FALSE
691 except Exception:
692 main.log.exception( self.name + ": Uncaught exception!" )
693 return main.FALSE
694
695 def dockerStop( self, containerName ):
696 """
697 Stop a docker container
698 Required Arguments:
699 - containerName: Name of the container to stop
700 """
701 try:
702 cmdStr = "docker stop %s" % ( containerName )
703 main.log.info( self.name + ": sending: " + cmdStr )
704 self.handle.sendline( cmdStr)
705 i = self.handle.expect( [ self.prompt,
706 "Error response from daemon",
707 pexpect.TIMEOUT ], 120 )
708 output = self.handle.before
709 if i == 0:
710 return main.TRUE
711 elif i == 1:
712 output += self.handle.after
713 self.handle.expect( self.prompt )
714 output += self.handle.before
715 elif i == 2:
716 pass
717 main.log.debug( "%s: %s" % ( self.name, output ) )
718 if "No such container" in output:
719 return main.TRUE
720 main.log.error( self.name + ": Error stopping docker image" )
721 main.log.debug( self.name + ": " + output )
722 return main.FALSE
723 except pexpect.EOF:
724 main.log.error( self.name + ": EOF exception found" )
725 main.log.error( self.name + ": " + self.handle.before )
726 return main.FALSE
727 except Exception:
728 main.log.exception( self.name + ": Uncaught exception!" )
729 return main.FALSE
730
731 def dockerRun( self, image, containerName, options="", imageArgs="" ):
732 """
733 Run a docker image
734 Required Arguments:
735 - containerName: Give a name to the container once its started
736 - image: Run the given image
737 Optional Arguments:
738 - options: A string containing any addition optional arguments
739 for the docker run command
740 - imageArgs: A string containing command line arguments for the
741 command run by docker
742 """
743 try:
744 cmdStr = "docker run --name %s %s %s %s" % ( containerName,
745 options if options else "",
746 image,
747 imageArgs )
748 main.log.info( self.name + ": sending: " + cmdStr )
749 self.handle.sendline( cmdStr)
750 i = self.handle.expect( [ self.prompt,
751 "Error response from daemon",
752 pexpect.TIMEOUT ], 120 )
753 if i == 0:
754 return main.TRUE
755 else:
756 output = self.handle.before
757 main.log.debug( self.name + ": " + output )
758 main.log.error( self.name + ": Error running docker image" )
759 if i == 1:
760 output += self.handle.after
761 self.handle.expect( self.prompt )
762 output += self.handle.before + self.handle.after
763 main.log.debug( self.name + ": " + output )
764 return main.FALSE
765 except pexpect.EOF:
766 main.log.error( self.name + ": EOF exception found" )
767 main.log.error( self.name + ": " + self.handle.before )
768 return main.FALSE
769 except Exception:
770 main.log.exception( self.name + ": Uncaught exception!" )
771 return main.FALSE
772
773 def dockerAttach( self, containerName, dockerPrompt="" ):
774 """
775 Attach to a docker image
776 Required Arguments:
777 - containerName: The name of the container to attach to
778 Optional Arguments:
779 - dockerPrompt: a regex for matching the docker shell prompt
780 """
781 try:
782 if dockerPrompt:
783 self.dockerPrompt = dockerPrompt
784 cmdStr = "docker attach %s" % containerName
785 main.log.info( self.name + ": sending: " + cmdStr )
786 self.handle.sendline( cmdStr)
787 i = self.handle.expect( [ self.dockerPrompt,
788 "Error response from daemon",
789 pexpect.TIMEOUT ] )
790 if i == 0:
791 self.inDocker = True
792 return main.TRUE
793 else:
794 main.log.error( self.name + ": Error connecting to docker container" )
795 output = self.handle.before + str( self.handle.after )
796 if i == 1:
797 self.handle.expect( self.prompt )
798 output += self.handle.before + str( self.handle.after )
799 main.log.debug( self.name + ": " + output )
800 return main.FALSE
801 except pexpect.EOF:
802 main.log.error( self.name + ": EOF exception found" )
803 main.log.error( self.name + ": " + self.handle.before )
804 return main.FALSE
805 except AttributeError as e:
806 main.log.exception( self.name + ": AttributeError - " + str( e ) )
807 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
808 main.cleanup()
809 main.exit()
810 except Exception:
811 main.log.exception( self.name + ": Uncaught exception!" )
812 return main.FALSE
813
814 def dockerExec( self, containerName, command="/bin/bash", options="-it", dockerPrompt="" ):
815 """
816 Attach to a docker image
817 Required Arguments:
818 - containerName: The name of the container to attach to
819 Optional Arguments:
820 - command: Command to run in the docker container
821 - options: Docker exec options
822 - dockerPrompt: a regex for matching the docker shell prompt
823 """
824 try:
825 if dockerPrompt:
826 self.dockerPrompt = dockerPrompt
827 cmdStr = "docker exec %s %s %s" % ( options, containerName, command )
828 main.log.info( self.name + ": sending: " + cmdStr )
829 self.handle.sendline( cmdStr)
830 i = self.handle.expect( [ self.dockerPrompt,
831 "Error response from daemon",
832 pexpect.TIMEOUT ] )
833 if i == 0:
834 self.inDocker = True
835 return main.TRUE
836 else:
837 main.log.error( self.name + ": Error connecting to docker container" )
838 output = self.handle.before + str( self.handle.after )
839 if i == 1:
840 self.handle.expect( self.prompt )
841 output += self.handle.before + str( self.handle.after )
842 main.log.debug( self.name + ": " + output )
843 return main.FALSE
844 except pexpect.EOF:
845 main.log.error( self.name + ": EOF exception found" )
846 main.log.error( self.name + ": " + self.handle.before )
847 return main.FALSE
848 except AttributeError as e:
849 main.log.exception( self.name + ": AttributeError - " + str( e ) )
850 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
851 main.cleanup()
852 main.exit()
853 except Exception:
854 main.log.exception( self.name + ": Uncaught exception!" )
855 return main.FALSE
856
857 def dockerCp( self, containerName, dockerPath, hostPath, direction="from" ):
858 """
859 Copy a file from/to a docker container to the host
860 Required Arguments:
861 - containerName: The name of the container to copy from/to
862 - dockerPath: the path in the container to copy from/to
863 - hostPath: the path on the host to copy to/from
864 Optional Arguments:
865 - direction: Choose whether to copy "from" the container or "to" the container
866 """
867 try:
868 cmdStr = "docker cp "
869 if direction == "from":
870 cmdStr += "%s:%s %s" % ( containerName, dockerPath, hostPath )
871 elif direction == "to":
872 cmdStr += "%s %s:%s" % ( hostPath, containerName, dockerPath )
873 main.log.info( self.name + ": sending: " + cmdStr )
874 self.handle.sendline( cmdStr)
875 i = self.handle.expect( [ self.prompt,
876 "Error",
877 pexpect.TIMEOUT ] )
878 if i == 0:
879 retValue = main.TRUE
880 else:
881 main.log.error( self.name + ": Error in docker cp" )
882 output = self.handle.before + str( self.handle.after )
883 if i == 1:
884 self.handle.expect( self.prompt )
885 output += self.handle.before + str( self.handle.after )
886 main.log.debug( self.name + ": " + output )
887 retValue = main.FALSE
888 return retValue
889 except pexpect.EOF:
890 main.log.error( self.name + ": EOF exception found" )
891 main.log.error( self.name + ": " + self.handle.before )
892 return main.FALSE
893 except AttributeError as e:
894 main.log.exception( self.name + ": AttributeError - " + str( e ) )
895 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
896 main.cleanup()
897 main.exit()
898 except Exception:
899 main.log.exception( self.name + ": Uncaught exception!" )
900 return main.FALSE
901
902 def dockerDisconnect( self ):
903 """
904 Send ctrl-c, ctrl-d to session, which should close and exit the
905 attached docker session. This will likely exit the running program
906 in the container and also stop the container.
907 """
908 try:
909 cmdStr = "\x03"
910 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
911 self.handle.send( cmdStr)
912 cmdStr = "\x04"
913 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
914 self.handle.send( cmdStr)
915 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
916 if i == 0:
917 self.inDocker = False
918 return main.TRUE
919 else:
920 main.log.error( self.name + ": Error disconnecting from docker image" )
921 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
922 return main.FALSE
923 except pexpect.EOF:
924 main.log.error( self.name + ": EOF exception found" )
925 main.log.error( self.name + ": " + self.handle.before )
926 return main.FALSE
927 except Exception:
928 main.log.exception( self.name + ": Uncaught exception!" )
929 return main.FALSE