blob: 7d306b99662a9bf7ca75a3fb7403bc4c5e6d5be6 [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
Jon Hall06fd0df2021-01-25 15:50:06 -080025import os
adminbae64d82013-08-01 10:50:15 -070026
27from drivers.component import Component
kelvin8ec71442015-01-15 16:57:00 -080028
29
30class CLI( Component ):
31
32 """
adminbae64d82013-08-01 10:50:15 -070033 This will define common functions for CLI included.
kelvin8ec71442015-01-15 16:57:00 -080034 """
35 def __init__( self ):
Devin Limdc78e202017-06-09 18:30:07 -070036 super( CLI, self ).__init__()
Jon Hall3c0114c2020-08-11 15:07:42 -070037 self.inDocker = False
Jon Hallca319892017-06-15 15:25:22 -070038
Jeremy Ronquillo82705492017-10-18 14:19:55 -070039 def checkPrompt( self ):
Devin Limdc78e202017-06-09 18:30:07 -070040 for key in self.options:
Jeremy Ronquillo82705492017-10-18 14:19:55 -070041 if key == "prompt" and self.options[ 'prompt' ] is not None:
42 self.prompt = self.options[ 'prompt' ]
Devin Limdc78e202017-06-09 18:30:07 -070043 break
kelvin8ec71442015-01-15 16:57:00 -080044
45 def connect( self, **connectargs ):
46 """
adminbae64d82013-08-01 10:50:15 -070047 Connection will establish to the remote host using ssh.
48 It will take user_name ,ip_address and password as arguments<br>
kelvin8ec71442015-01-15 16:57:00 -080049 and will return the handle.
50 """
Jon Hall06fd0df2021-01-25 15:50:06 -080051 self.shell = "/bin/bash -l"
adminbae64d82013-08-01 10:50:15 -070052 for key in connectargs:
kelvin8ec71442015-01-15 16:57:00 -080053 vars( self )[ key ] = connectargs[ key ]
Devin Limdc78e202017-06-09 18:30:07 -070054 self.checkPrompt()
adminbae64d82013-08-01 10:50:15 -070055
kelvin8ec71442015-01-15 16:57:00 -080056 connect_result = super( CLI, self ).connect()
adminbae64d82013-08-01 10:50:15 -070057 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -080058 refused = "ssh: connect to host " + \
59 self.ip_address + " port 22: Connection refused"
Jon Hall06fd0df2021-01-25 15:50:06 -080060 ssh_options = "-t -X -A -o ServerAliveInterval=120 -o TCPKeepAlive=yes"
61 ssh_destination = self.user_name + "@" + self.ip_address
62 envVars = { "TERM": "vt100" }
63 # TODO: Add option to specify which shell/command to use
64 jump_host = main.componentDictionary[ self.name ].get( 'jump_host' )
adminbae64d82013-08-01 10:50:15 -070065 if self.port:
Jon Hall06fd0df2021-01-25 15:50:06 -080066 ssh_option += " -p " + self.port
67 if jump_host:
68 jump_host = main.componentDictionary.get( jump_host )
69 ssh_options += " -J %s@%s" % ( jump_host.get( 'user' ), jump_host.get( 'host' ) )
70 ssh_auth = os.getenv('SSH_AUTH_SOCK')
71 if ssh_auth:
72 envVars[ 'SSH_AUTH_SOCK' ] = ssh_auth
73 self.handle = pexpect.spawn(
74 "ssh %s %s %s" % ( ssh_options, ssh_destination, self.shell ),
75 env=envVars,
76 maxread=1000000,
77 timeout=60 )
adminbae64d82013-08-01 10:50:15 -070078
Jon Hall73057ee2016-08-23 09:57:26 -070079 # set tty window size
80 self.handle.setwinsize( 24, 250 )
81
adminbae64d82013-08-01 10:50:15 -070082 self.handle.logfile = self.logfile_handler
kelvin8ec71442015-01-15 16:57:00 -080083 i = 5
84 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -070085 i = self.handle.expect( [ ssh_newkey,
86 'password:|Password:',
87 pexpect.EOF,
88 pexpect.TIMEOUT,
89 refused,
90 'teston>',
Jon Halld9066132018-03-01 14:52:53 -080091 'Permission denied, please try again.',
Jon Hall4173b242017-09-12 17:04:38 -070092 self.prompt ],
93 120 )
acsmars32de0bc2015-06-30 09:57:12 -070094 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -070095 main.log.info( self.name + ": ssh key confirmation received, send yes" )
kelvin8ec71442015-01-15 16:57:00 -080096 self.handle.sendline( 'yes' )
acsmars32de0bc2015-06-30 09:57:12 -070097 i = 5 # Run the loop again
acsmars07f9d392015-07-15 10:30:58 -070098 continue
99 if i == 1: # Password required
Jon Hall63604932015-02-26 17:09:50 -0800100 if self.pwd:
101 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700102 "ssh connection asked for password, gave password" )
Jon Hall63604932015-02-26 17:09:50 -0800103 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700104 main.log.info( self.name + ": Server asked for password, but none was "
acsmars07f9d392015-07-15 10:30:58 -0700105 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700106 "no password." )
acsmars07f9d392015-07-15 10:30:58 -0700107 self.pwd = ""
108 self.handle.sendline( self.pwd )
109 j = self.handle.expect( [
acsmars07f9d392015-07-15 10:30:58 -0700110 'password:|Password:',
Jon Halld9066132018-03-01 14:52:53 -0800111 'Permission denied, please try again.',
112 self.prompt,
acsmars07f9d392015-07-15 10:30:58 -0700113 pexpect.EOF,
114 pexpect.TIMEOUT ],
115 120 )
Jon Halld9066132018-03-01 14:52:53 -0800116 if j != 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700117 main.log.error( self.name + ": Incorrect Password" )
acsmars07f9d392015-07-15 10:30:58 -0700118 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800119 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700120 main.log.error( self.name + ": Connection timeout" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800121 main.log.debug( self.handle.before )
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 )
Jon Hall06fd0df2021-01-25 15:50:06 -0800129 main.log.debug( self.handle.before )
kelvin8ec71442015-01-15 16:57:00 -0800130 return main.FALSE
131 elif i == 4:
132 main.log.error(
133 "ssh: connect to host " +
134 self.ip_address +
135 " port 22: Connection refused" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800136 main.log.debug( self.handle.before )
kelvin8ec71442015-01-15 16:57:00 -0800137 return main.FALSE
Jon Halld9066132018-03-01 14:52:53 -0800138 elif i == 6: # Incorrect Password
Jon Hall3c0114c2020-08-11 15:07:42 -0700139 main.log.error( self.name + ": Incorrect Password" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800140 main.log.debug( self.handle.before )
Jon Halld9066132018-03-01 14:52:53 -0800141 return main.FALSE
142 elif i == 7: # Prompt
Jon Hall3c0114c2020-08-11 15:07:42 -0700143 main.log.info( self.name + ": Password not required logged in" )
adminbae64d82013-08-01 10:50:15 -0700144
kelvin8ec71442015-01-15 16:57:00 -0800145 self.handle.sendline( "" )
Devin Limdc78e202017-06-09 18:30:07 -0700146 self.handle.expect( self.prompt )
Jeremy Ronquillo0f2008a2017-06-23 15:32:51 -0700147 self.handle.sendline( "cd" )
148 self.handle.expect( self.prompt )
adminbae64d82013-08-01 10:50:15 -0700149 return self.handle
150
kelvin8ec71442015-01-15 16:57:00 -0800151 def disconnect( self ):
Jon Hall06fd0df2021-01-25 15:50:06 -0800152 result = self.preDisconnect()
kelvin8ec71442015-01-15 16:57:00 -0800153 result = super( CLI, self ).disconnect( self )
adminbae64d82013-08-01 10:50:15 -0700154 result = main.TRUE
Jon Hall3c0114c2020-08-11 15:07:42 -0700155
156 def Prompt( self ):
157 """
158 Returns the prompt to expect depending on what program we are in
159 """
160 return self.prompt if not self.inDocker else self.dockerPrompt
kelvin8ec71442015-01-15 16:57:00 -0800161
162 def execute( self, **execparams ):
163 """
adminbae64d82013-08-01 10:50:15 -0700164 It facilitates the command line execution of a given command. It has arguments as :
165 cmd => represents command to be executed,
166 prompt => represents expect command prompt or output,
167 timeout => timeout for command execution,
168 more => to provide a key press if it is on.
You Wang7d14d642019-01-23 15:10:08 -0800169 logCmd => log the command executed if True
adminbae64d82013-08-01 10:50:15 -0700170
171 It will return output of command exection.
kelvin8ec71442015-01-15 16:57:00 -0800172 """
173 result = super( CLI, self ).execute( self )
adminaef00552014-05-08 09:18:36 -0700174 defaultPrompt = '.*[$>\#]'
Jon Hall3b489db2015-10-05 14:38:37 -0700175 args = utilities.parse_args( [ "CMD",
176 "TIMEOUT",
177 "PROMPT",
You Wang7d14d642019-01-23 15:10:08 -0800178 "MORE",
179 "LOGCMD" ],
Jon Hall3b489db2015-10-05 14:38:37 -0700180 **execparams )
kelvin8ec71442015-01-15 16:57:00 -0800181
182 expectPrompt = args[ "PROMPT" ] if args[ "PROMPT" ] else defaultPrompt
adminbae64d82013-08-01 10:50:15 -0700183 self.LASTRSP = ""
kelvin8ec71442015-01-15 16:57:00 -0800184 timeoutVar = args[ "TIMEOUT" ] if args[ "TIMEOUT" ] else 10
adminbae64d82013-08-01 10:50:15 -0700185 cmd = ''
kelvin8ec71442015-01-15 16:57:00 -0800186 if args[ "CMD" ]:
187 cmd = args[ "CMD" ]
188 else:
adminbae64d82013-08-01 10:50:15 -0700189 return 0
kelvin8ec71442015-01-15 16:57:00 -0800190 if args[ "MORE" ] is None:
191 args[ "MORE" ] = " "
192 self.handle.sendline( cmd )
adminbae64d82013-08-01 10:50:15 -0700193 self.lastCommand = cmd
Jon Hall3b489db2015-10-05 14:38:37 -0700194 index = self.handle.expect( [ expectPrompt,
195 "--More--",
196 'Command not found.',
197 pexpect.TIMEOUT,
198 "^:$" ],
199 timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700200 if index == 0:
kelvin8ec71442015-01-15 16:57:00 -0800201 self.LASTRSP = self.LASTRSP + \
202 self.handle.before + self.handle.after
You Wang7d14d642019-01-23 15:10:08 -0800203 if not args[ "LOGCMD" ] is False:
Jon Hall3c0114c2020-08-11 15:07:42 -0700204 main.log.info( self.name + ": Executed :" + str( cmd ) +
You Wang7d14d642019-01-23 15:10:08 -0800205 " \t\t Expected Prompt '" + str( expectPrompt ) +
206 "' Found" )
adminbae64d82013-08-01 10:50:15 -0700207 elif index == 1:
208 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800209 self.handle.send( args[ "MORE" ] )
210 main.log.info(
211 "Found More screen to go , Sending a key to proceed" )
212 indexMore = self.handle.expect(
213 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700214 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800215 main.log.info(
216 "Found anoother More screen to go , Sending a key to proceed" )
217 self.handle.send( args[ "MORE" ] )
218 indexMore = self.handle.expect(
219 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700220 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800221 elif index == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700222 main.log.error( self.name + ": Command not found" )
adminbae64d82013-08-01 10:50:15 -0700223 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800224 elif index == 3:
Jon Hall3c0114c2020-08-11 15:07:42 -0700225 main.log.error( self.name + ": Expected Prompt not found, Time Out!!" )
kelvin8ec71442015-01-15 16:57:00 -0800226 main.log.error( expectPrompt )
Jon Hall3b489db2015-10-05 14:38:37 -0700227 self.LASTRSP = self.LASTRSP + self.handle.before
228 return self.LASTRSP
adminbae64d82013-08-01 10:50:15 -0700229 elif index == 4:
230 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800231 # self.handle.send( args[ "MORE" ] )
232 self.handle.sendcontrol( "D" )
233 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700234 "Found More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800235 indexMore = self.handle.expect(
236 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700237 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800238 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700239 "Found another More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800240 self.handle.sendcontrol( "D" )
241 indexMore = self.handle.expect(
242 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700243 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800244 main.last_response = self.remove_contol_chars( self.LASTRSP )
adminbae64d82013-08-01 10:50:15 -0700245 return self.LASTRSP
kelvin8ec71442015-01-15 16:57:00 -0800246
247 def remove_contol_chars( self, response ):
248 # 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 ) )
249 # response = re.sub( RE_XML_ILLEGAL, "\n", response )
250 response = re.sub( r"[\x01-\x1F\x7F]", "", response )
251 # response = re.sub( r"\[\d+\;1H", "\n", response )
252 response = re.sub( r"\[\d+\;\d+H", "", response )
adminbae64d82013-08-01 10:50:15 -0700253 return response
adminbae64d82013-08-01 10:50:15 -0700254
kelvin8ec71442015-01-15 16:57:00 -0800255 def runAsSudoUser( self, handle, pwd, default ):
256
257 i = handle.expect( [ ".ssword:*", default, pexpect.EOF ] )
258 if i == 0:
259 handle.sendline( pwd )
Jon Hall5ec6b1b2015-09-17 18:20:14 -0700260 handle.sendline( "\n" )
kelvin8ec71442015-01-15 16:57:00 -0800261
262 if i == 1:
263 handle.expect( default )
264
265 if i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700266 main.log.error( self.name + ": Unable to run as Sudo user" )
kelvin8ec71442015-01-15 16:57:00 -0800267
adminbae64d82013-08-01 10:50:15 -0700268 return handle
adminbae64d82013-08-01 10:50:15 -0700269
kelvin8ec71442015-01-15 16:57:00 -0800270 def onfail( self ):
271 if 'onfail' in main.componentDictionary[ self.name ]:
272 commandList = main.componentDictionary[
273 self.name ][ 'onfail' ].split( "," )
274 for command in commandList:
275 response = self.execute(
276 cmd=command,
277 prompt="(.*)",
278 timeout=120 )
adminbae64d82013-08-01 10:50:15 -0700279
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700280 def secureCopy( self, userName, ipAddress, filePath, dstPath, pwd="",
Jon Hall22a3bcf2021-07-23 11:40:11 -0700281 direction="from", options="", timeout=120 ):
kelvin8ec71442015-01-15 16:57:00 -0800282 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700283 Definition:
284 Execute scp command in linux to copy to/from a remote host
285 Required:
286 str userName - User name of the remote host
287 str ipAddress - IP address of the remote host
288 str filePath - File path including the file it self
289 str dstPath - Destination path
290 Optional:
291 str pwd - Password of the host
292 str direction - Direction of the scp, default to "from" which means
293 copy "from" the remote machine to local machine,
294 while "to" means copy "to" the remote machine from
295 local machine
kelvin8ec71442015-01-15 16:57:00 -0800296 """
Jon Hall669bc862021-03-09 12:24:44 -0800297 returnVal = main.FALSE
adminbae64d82013-08-01 10:50:15 -0700298 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -0800299 refused = "ssh: connect to host " + \
Jon Hall547e0582015-09-21 17:35:40 -0700300 ipAddress + " port 22: Connection refused"
Jon Hall39570262020-11-17 12:18:19 -0800301 cmd = "scp %s " % options
Jon Hall669bc862021-03-09 12:24:44 -0800302 try:
303 self.handle.sendline( "" )
304 self.handle.expect( self.prompt, timeout=5 )
305 except pexpect.TIMEOUT:
306 main.log.error( "%s: Component not ready for input" % self.name )
307 main.log.debug( "%s: %s%s" % ( self.name, self.handle.before, str( self.handle.after ) ) )
308 self.handle.send( "\x03" ) # CTRL-C
309 self.handle.expect( self.prompt, timeout=5 )
acsmars07f9d392015-07-15 10:30:58 -0700310
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700311 if direction == "from":
Jon Hall39570262020-11-17 12:18:19 -0800312 cmd = cmd + str( userName ) + '@' + str( ipAddress ) + ':' + \
Jon Hall547e0582015-09-21 17:35:40 -0700313 str( filePath ) + ' ' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700314 elif direction == "to":
Jon Hall39570262020-11-17 12:18:19 -0800315 cmd = cmd + str( filePath ) + ' ' + str( userName ) + \
Jon Hall547e0582015-09-21 17:35:40 -0700316 '@' + str( ipAddress ) + ':' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700317 else:
318 main.log.debug( "Wrong direction using secure copy command!" )
319 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800320
Jon Hall3c0114c2020-08-11 15:07:42 -0700321 main.log.info( self.name + ": Sending: " + cmd )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700322 self.handle.sendline( cmd )
Jon Hall547e0582015-09-21 17:35:40 -0700323 i = 0
Jon Hall66ce22f2021-06-30 14:57:40 -0700324 hit = False
Jon Hall669bc862021-03-09 12:24:44 -0800325 while i <= 6 :
Jon Hall547e0582015-09-21 17:35:40 -0700326 i = self.handle.expect( [
327 ssh_newkey,
328 'password:',
329 "100%",
330 refused,
331 "No such file or directory",
Jon Hall53c5e662016-04-13 16:06:56 -0700332 "Permission denied",
Devin Limdc78e202017-06-09 18:30:07 -0700333 self.prompt,
Jon Hall547e0582015-09-21 17:35:40 -0700334 pexpect.EOF,
335 pexpect.TIMEOUT ],
Jon Hall669bc862021-03-09 12:24:44 -0800336 timeout=timeout )
Jon Hall547e0582015-09-21 17:35:40 -0700337 if i == 0: # ask for ssh key confirmation
Jon Hall66ce22f2021-06-30 14:57:40 -0700338 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700339 main.log.info( self.name + ": ssh key confirmation received, sending yes" )
Jon Hall547e0582015-09-21 17:35:40 -0700340 self.handle.sendline( 'yes' )
341 elif i == 1: # Asked for ssh password
Jon Hall66ce22f2021-06-30 14:57:40 -0700342 hit = True
Jon Hall669bc862021-03-09 12:24:44 -0800343 timeout = 120
Jon Hall3c0114c2020-08-11 15:07:42 -0700344 main.log.info( self.name + ": ssh connection asked for password, gave password" )
Jon Hall547e0582015-09-21 17:35:40 -0700345 self.handle.sendline( pwd )
346 elif i == 2: # File finished transfering
Jon Hall66ce22f2021-06-30 14:57:40 -0700347 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700348 main.log.info( self.name + ": Secure copy successful" )
Jon Hall669bc862021-03-09 12:24:44 -0800349 timeout = 10
Jon Hall547e0582015-09-21 17:35:40 -0700350 returnVal = main.TRUE
351 elif i == 3: # Connection refused
Jon Hall66ce22f2021-06-30 14:57:40 -0700352 hit = True
Jon Hall547e0582015-09-21 17:35:40 -0700353 main.log.error(
354 "ssh: connect to host " +
355 ipAddress +
356 " port 22: Connection refused" )
357 returnVal = main.FALSE
358 elif i == 4: # File Not found
Jon Hall66ce22f2021-06-30 14:57:40 -0700359 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700360 main.log.error( self.name + ": No such file found" )
Jon Hall39570262020-11-17 12:18:19 -0800361 main.log.debug( self.handle.before + self.handle.after )
Jon Hall547e0582015-09-21 17:35:40 -0700362 returnVal = main.FALSE
Jon Hall53c5e662016-04-13 16:06:56 -0700363 elif i == 5: # Permission denied
Jon Hall66ce22f2021-06-30 14:57:40 -0700364 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700365 main.log.error( self.name + ": Permission denied. Check folder permissions" )
Jon Hall39570262020-11-17 12:18:19 -0800366 main.log.debug( self.handle.before + self.handle.after )
Jon Hall53c5e662016-04-13 16:06:56 -0700367 returnVal = main.FALSE
368 elif i == 6: # prompt returned
Jon Hall66ce22f2021-06-30 14:57:40 -0700369 hit = True
Jon Hall669bc862021-03-09 12:24:44 -0800370 timeout = 10
371 main.log.debug( "%s: %s%s" % ( self.name, repr( self.handle.before ), repr( self.handle.after ) ) )
Jon Hall53c5e662016-04-13 16:06:56 -0700372 elif i == 7: # EOF
Jon Hall66ce22f2021-06-30 14:57:40 -0700373 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700374 main.log.error( self.name + ": Pexpect.EOF found!!!" )
Devin Lim44075962017-08-11 10:56:37 -0700375 main.cleanAndExit()
Jon Hall53c5e662016-04-13 16:06:56 -0700376 elif i == 8: # timeout
Jon Hall66ce22f2021-06-30 14:57:40 -0700377 if not hit:
Jon Hall669bc862021-03-09 12:24:44 -0800378 main.log.error(
379 "No route to the Host " +
380 userName +
381 "@" +
382 ipAddress )
383 return returnVal
384 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ], timeout=5 )
385 main.log.debug( "%s: %s%s" % ( self.name, repr( self.handle.before ), repr( self.handle.after ) ) )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700386 return returnVal
387
Jon Hall22a3bcf2021-07-23 11:40:11 -0700388 def scp( self, remoteHost, filePath, dstPath, direction="from", options="", timeout=120 ):
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700389 """
390 Definition:
391 Execute scp command in linux to copy to/from a remote host
392 Required:
393 * remoteHost - Test ON component to be parsed
394 str filePath - File path including the file it self
395 str dstPath - Destination path
396 Optional:
397 str direction - Direction of the scp, default to "from" which means
398 copy "from" the remote machine to local machine,
399 while "to" means copy "to" the remote machine from
400 local machine
401 """
Jon Hall06fd0df2021-01-25 15:50:06 -0800402 jump_host = main.componentDictionary[ remoteHost.name ].get( 'jump_host' )
403 if jump_host:
404 jump_host = main.componentDictionary.get( jump_host )
Jon Hall669bc862021-03-09 12:24:44 -0800405 options += " -o 'ProxyJump %s@%s' " % ( jump_host.get( 'user' ), jump_host.get( 'host' ) )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700406 return self.secureCopy( remoteHost.user_name,
407 remoteHost.ip_address,
408 filePath,
409 dstPath,
410 pwd=remoteHost.pwd,
Jon Hall39570262020-11-17 12:18:19 -0800411 direction=direction,
Jon Hall22a3bcf2021-07-23 11:40:11 -0700412 options=options,
413 timeout=timeout )
Devin Lim142b5342017-07-20 15:22:39 -0700414
415 def sshToNode( self, ipAddress, uName="sdn", pwd="rocks" ):
416 ssh_newkey = 'Are you sure you want to continue connecting'
417 refused = "ssh: connect to host " + ipAddress + " port 22: Connection refused"
418 handle = pexpect.spawn( 'ssh -X ' +
419 uName +
420 '@' +
421 ipAddress,
Jon Hall6c9e2da2018-11-06 12:01:23 -0800422 env={ "TERM": "vt100" },
Devin Lim142b5342017-07-20 15:22:39 -0700423 maxread=1000000,
424 timeout=60 )
425
426 # set tty window size
427 handle.setwinsize( 24, 250 )
428
429 i = 5
430 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -0700431 i = handle.expect( [ ssh_newkey,
432 'password:|Password:',
433 pexpect.EOF,
434 pexpect.TIMEOUT,
435 refused,
436 'teston>',
437 self.prompt ],
438 120 )
Devin Lim142b5342017-07-20 15:22:39 -0700439 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -0700440 main.log.info( self.name + ": ssh key confirmation received, send yes" )
Devin Lim142b5342017-07-20 15:22:39 -0700441 handle.sendline( 'yes' )
442 i = 5 # Run the loop again
443 continue
444 if i == 1: # Password required
445 if pwd:
446 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700447 "ssh connection asked for password, gave password" )
Devin Lim142b5342017-07-20 15:22:39 -0700448 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700449 main.log.info( self.name + ": Server asked for password, but none was "
Devin Lim142b5342017-07-20 15:22:39 -0700450 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700451 "no password." )
Devin Lim142b5342017-07-20 15:22:39 -0700452 pwd = ""
453 handle.sendline( pwd )
454 j = handle.expect( [ self.prompt,
455 'password:|Password:',
456 pexpect.EOF,
457 pexpect.TIMEOUT ],
458 120 )
459 if j != 0:
Jon Hall3c0114c2020-08-11 15:07:42 -0700460 main.log.error( self.name + ": Incorrect Password" )
Devin Lim44075962017-08-11 10:56:37 -0700461 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700462 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700463 main.log.error( self.name + ": Connection timeout" )
Devin Lim44075962017-08-11 10:56:37 -0700464 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700465 elif i == 3: # timeout
466 main.log.error(
467 "No route to the Host " +
468 uName +
469 "@" +
470 ipAddress )
Devin Lim44075962017-08-11 10:56:37 -0700471 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700472 elif i == 4:
473 main.log.error(
474 "ssh: connect to host " +
475 ipAddress +
476 " port 22: Connection refused" )
Devin Lim44075962017-08-11 10:56:37 -0700477 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700478 elif i == 6:
Jon Hall3c0114c2020-08-11 15:07:42 -0700479 main.log.info( self.name + ": Password not required logged in" )
Devin Lim142b5342017-07-20 15:22:39 -0700480
481 handle.sendline( "" )
482 handle.expect( self.prompt )
483 handle.sendline( "cd" )
484 handle.expect( self.prompt )
485
Jon Hall3c0114c2020-08-11 15:07:42 -0700486 main.log.info( self.name + ": Successfully ssh to " + ipAddress + "." )
Devin Lim142b5342017-07-20 15:22:39 -0700487 return handle
488
489 def exitFromSsh( self, handle, ipAddress ):
Devin Lim142b5342017-07-20 15:22:39 -0700490 try:
Jon Hall4f360bc2017-09-07 10:19:52 -0700491 handle.sendline( "logout" )
Devin Lim142b5342017-07-20 15:22:39 -0700492 handle.expect( "closed." )
Jon Hall3c0114c2020-08-11 15:07:42 -0700493 main.log.info( self.name + ": Successfully closed ssh connection from " + ipAddress )
Devin Lim142b5342017-07-20 15:22:39 -0700494 except pexpect.EOF:
Jon Hall3c0114c2020-08-11 15:07:42 -0700495 main.log.error( self.name + ": Failed to close the connection from " + ipAddress )
Jon Hall4f360bc2017-09-07 10:19:52 -0700496 try:
497 # check that this component handle still works
498 self.handle.sendline( "" )
499 self.handle.expect( self.prompt )
500 except pexpect.EOF:
501 main.log.error( self.handle.before )
Jon Hall3c0114c2020-08-11 15:07:42 -0700502 main.log.error( self.name + ": EOF after closing ssh connection" )
Jon Hall4173b242017-09-12 17:04:38 -0700503
504 def folderSize( self, path, size='10', unit='M', ignoreRoot=True ):
505 """
506 Run `du -h` on the folder path and verifies the folder(s) size is
507 less than the given size. Note that if multiple subdirectories are
508 present, the result will be the OR of all the individual subdirectories.
509
510 Arguments:
511 path - A string containing the path supplied to the du command
512 size - The number portion of the file size that the results will be compared to
513 unit - The unit portion of the file size that the results will be compared to
514 ignoreRoot - If True, will ignore the "root" of the path supplied to du. I.E. will ignore `.`
515
516 Returns True if the folder(s) size(s) are less than SIZE UNITS, else returns False
517 """
518 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
519 unitsList = [ 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]
520 try:
521 # make sure we convert units if size is too big
522 size = float( size )
523 if size >= 1000:
524 size = size / 1000
525 unit = unitsList[ unitsList.index( unit + 1 ) ]
526 cmdStr = "du -h " + path
527 self.handle.sendline( cmdStr )
528 self.handle.expect( self.prompt )
529 output = self.handle.before
530 assert "cannot access" not in output
531 assert "command not found" not in output
532 main.log.debug( output )
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700533 lines = [ line for line in output.split( '\r\n' ) ]
Jon Hall4173b242017-09-12 17:04:38 -0700534 retValue = True
535 if ignoreRoot:
536 lastIndex = -2
537 else:
538 lastIndex = -1
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700539 for line in lines[ 1:lastIndex ]:
Jon Hall4173b242017-09-12 17:04:38 -0700540 parsed = line.split()
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700541 sizeMatch = parsed[ 0 ]
542 folder = parsed[ 1 ]
Jon Hall4173b242017-09-12 17:04:38 -0700543 match = re.search( sizeRe, sizeMatch )
544 num = match.group( 'number' )
545 unitMatch = match.group( 'unit' )
546 if unitsList.index( unitMatch ) < unitsList.index( unit ):
547 retValue &= True
548 elif unitsList.index( unitMatch ) == unitsList.index( unit ):
549 if float( num ) < float( size ):
550 retValue &= True
551 else:
552 retValue &= False
553 elif unitsList.index( unitMatch ) > unitsList.index( unit ):
554 retValue &= False
555 return retValue
556 except AssertionError:
557 main.log.error( self.name + ": Could not execute command: " + output )
558 return False
Jon Hall43060f62020-06-23 13:13:33 -0700559 except ValueError as e:
560 main.log.error( self.name + ": Error parsing output: " + output )
561 main.log.error( e )
562 return False
Jon Hall4173b242017-09-12 17:04:38 -0700563 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()
Jon Hall0e240372018-05-02 11:21:57 -0700571
Jon Hall22a3bcf2021-07-23 11:40:11 -0700572 def fileSize( self, path, inBytes=True ):
573 """
574 Run `du` on the file path and returns the file size
575
576 Arguments:
577 path - A string containing the path supplied to the du command
578 Optional Arguments:
579 inBytes - Display size in bytes, defaults to true
580
581 Returns the size of the file as an int
582 """
583 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
584 try:
585 cmdStr = "du %s %s" % ( "-b" if inBytes else "", path )
586 self.handle.sendline( cmdStr )
587 self.handle.expect( self.prompt )
588 output = self.handle.before
589 assert "cannot access" not in output
590 assert "command not found" not in output
591 assert "No such file or directory" not in output
592 main.log.debug( output )
593 lines = [ line for line in output.split( '\r\n' ) ]
594 return int( lines[1].split()[0] )
595 except AssertionError:
596 main.log.error( self.name + ": Could not execute command: " + output )
597 return False
598 except ValueError as e:
599 main.log.error( self.name + ": Error parsing output: " + output )
600 main.log.error( e )
601 return False
602 except pexpect.TIMEOUT:
603 main.log.exception( self.name + ": TIMEOUT exception found" )
604 main.log.error( self.name + ": " + self.handle.before )
605 return False
606 except pexpect.EOF:
607 main.log.error( self.name + ": EOF exception found" )
608 main.log.error( self.name + ": " + self.handle.before )
609 main.cleanAndExit()
610
Jon Hall0e240372018-05-02 11:21:57 -0700611 def setEnv( self, variable, value=None ):
612 """
613 Sets the environment variable to the given value for the current shell session.
614 If value is None, will unset the variable.
615
616 Required Arguments:
617 variable - The name of the environment variable to set.
618
619 Optional Arguments:
620 value - The value to set the variable to. ( Defaults to None, which unsets the variable )
621
622 Returns True if no errors are detected else returns False
623 """
624 try:
625 if value:
626 cmd = "export {}={}".format( variable, value )
627 else:
628 cmd = "unset {}".format( variable )
629 self.handle.sendline( cmd )
630 self.handle.expect( self.prompt )
Jon Hall3c0114c2020-08-11 15:07:42 -0700631 output = self.handle.before
632 main.log.debug( output )
Jon Hall0e240372018-05-02 11:21:57 -0700633 return True
634 except AssertionError:
635 main.log.error( self.name + ": Could not execute command: " + output )
636 return False
637 except pexpect.TIMEOUT:
638 main.log.exception( self.name + ": TIMEOUT exception found" )
639 main.log.error( self.name + ": " + self.handle.before )
640 return False
641 except pexpect.EOF:
642 main.log.error( self.name + ": EOF exception found" )
643 main.log.error( self.name + ": " + self.handle.before )
644 main.cleanAndExit()
You Wangb65d2372018-08-17 15:37:59 -0700645
646 def exitFromCmd( self, expect, retry=10 ):
647 """
648 Call this function when sending ctrl+c is required to kill the current
649 command. It will retry multiple times until the running command is
650 completely killed and expected string is returned from the handle.
651 Required:
You Wangd4fae5c2018-08-22 13:56:49 -0700652 expect: expected string or list of strings which indicates that the
653 previous command was killed successfully.
You Wangb65d2372018-08-17 15:37:59 -0700654 Optional:
655 retry: maximum number of ctrl+c that will be sent.
656 """
You Wangd4fae5c2018-08-22 13:56:49 -0700657 expect = [ expect ] if isinstance( expect, str ) else expect
You Wangb65d2372018-08-17 15:37:59 -0700658 try:
659 while retry >= 0:
660 main.log.debug( self.name + ": sending ctrl+c to kill the command" )
661 self.handle.send( "\x03" )
You Wangd4fae5c2018-08-22 13:56:49 -0700662 i = self.handle.expect( expect + [ pexpect.TIMEOUT ], timeout=3 )
You Wangb65d2372018-08-17 15:37:59 -0700663 main.log.debug( self.handle.before )
You Wangd4fae5c2018-08-22 13:56:49 -0700664 if i < len( expect ):
You Wangb65d2372018-08-17 15:37:59 -0700665 main.log.debug( self.name + ": successfully killed the command" )
666 return main.TRUE
667 retry -= 1
668 main.log.warn( self.name + ": failed to kill the command" )
669 return main.FALSE
670 except pexpect.EOF:
671 main.log.error( self.name + ": EOF exception found" )
672 main.log.error( self.name + ": " + self.handle.before )
673 return main.FALSE
674 except Exception:
675 main.log.exception( self.name + ": Uncaught exception!" )
676 return main.FALSE
Jon Hall43060f62020-06-23 13:13:33 -0700677
678 def cleanOutput( self, output, debug=False ):
679 """
680 Clean ANSI characters from output
681 """
682 ansiEscape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
683 cleaned = ansiEscape.sub( '', output )
684 if debug:
685 main.log.debug( self.name + ": cleanOutput:" )
686 main.log.debug( self.name + ": " + repr( cleaned ) )
687 return cleaned
Jon Hall3c0114c2020-08-11 15:07:42 -0700688
689 def dockerPull( self, image, tag=None ):
690 """
691 Pull a docker image from a registry
692 """
693 try:
694 imgStr = "%s%s" % ( image, ":%s" % tag if tag else "" )
695 cmdStr = "docker pull %s" % imgStr
696 main.log.info( self.name + ": sending: " + cmdStr )
697 self.handle.sendline( cmdStr)
698 i = self.handle.expect( [ self.prompt,
699 "Error response from daemon",
700 pexpect.TIMEOUT ], 120 )
701 if i == 0:
702 return main.TRUE
703 else:
704 main.log.error( self.name + ": Error pulling docker image " + imgStr )
705 output = self.handle.before + str( self.handle.after )
706 if i == 1:
707 self.handle.expect( self.prompt )
708 output += self.handle.before + str( self.handle.after )
709 main.log.debug( self.name + ": " + output )
710 return main.FALSE
711 except pexpect.EOF:
712 main.log.error( self.name + ": EOF exception found" )
713 main.log.error( self.name + ": " + self.handle.before )
714 return main.FALSE
715 except Exception:
716 main.log.exception( self.name + ": Uncaught exception!" )
717 return main.FALSE
718
719 def dockerBuild( self, path, imageTag, pull=False, options="", timeout=600 ):
720 """
721 Build a docker image
722 Required Arguments:
723 - path: Path to the dockerfile, it is recommended to avoid relative paths
724 - imageTag: Give a tag to the built docker image
725 Optional Arguments:
726 - pull: Whether to attempt to pull latest images before building
727 - options: A string containing any addition optional arguments
728 for the docker build command
729 - timeout: How many seconds to wait for the build to complete
730 """
731 try:
732 response = main.TRUE
733 if pull:
734 options = "--pull " + options
735 cmdStr = "docker build -t %s %s %s" % ( imageTag, options, path )
736 main.log.info( self.name + ": sending: " + cmdStr )
737 self.handle.sendline( cmdStr)
738 i = self.handle.expect( [ "Successfully built",
739 "Error response from daemon",
740 pexpect.TIMEOUT ], timeout=timeout )
741 output = self.handle.before
742 if i == 0:
743 output += self.handle.after
744 self.handle.expect( self.prompt )
745 output += self.handle.before + self.handle.after
746 return response
747 elif i == 1:
748 response = main.FALSE
749 output += self.handle.after
750 self.handle.expect( self.prompt )
751 output += self.handle.before + self.handle.after
752 elif i == 2:
753 response = main.FALSE
754 main.log.error( self.name + ": Error building docker image" )
755 main.log.debug( self.name + ": " + output )
756 return response
757 except pexpect.EOF:
758 main.log.error( self.name + ": EOF exception found" )
759 main.log.error( self.name + ": " + self.handle.before )
760 return main.FALSE
761 except Exception:
762 main.log.exception( self.name + ": Uncaught exception!" )
763 return main.FALSE
764
765 def dockerStop( self, containerName ):
766 """
767 Stop a docker container
768 Required Arguments:
769 - containerName: Name of the container to stop
770 """
771 try:
772 cmdStr = "docker stop %s" % ( containerName )
773 main.log.info( self.name + ": sending: " + cmdStr )
774 self.handle.sendline( cmdStr)
775 i = self.handle.expect( [ self.prompt,
776 "Error response from daemon",
777 pexpect.TIMEOUT ], 120 )
778 output = self.handle.before
779 if i == 0:
780 return main.TRUE
781 elif i == 1:
782 output += self.handle.after
783 self.handle.expect( self.prompt )
784 output += self.handle.before
785 elif i == 2:
786 pass
787 main.log.debug( "%s: %s" % ( self.name, output ) )
788 if "No such container" in output:
789 return main.TRUE
790 main.log.error( self.name + ": Error stopping docker image" )
791 main.log.debug( self.name + ": " + output )
792 return main.FALSE
793 except pexpect.EOF:
794 main.log.error( self.name + ": EOF exception found" )
795 main.log.error( self.name + ": " + self.handle.before )
796 return main.FALSE
797 except Exception:
798 main.log.exception( self.name + ": Uncaught exception!" )
799 return main.FALSE
800
Jon Hall06fd0df2021-01-25 15:50:06 -0800801 def dockerRun( self, image, containerName, options="", imageArgs="", background=False ):
Jon Hall3c0114c2020-08-11 15:07:42 -0700802 """
803 Run a docker image
804 Required Arguments:
805 - containerName: Give a name to the container once its started
806 - image: Run the given image
807 Optional Arguments:
808 - options: A string containing any addition optional arguments
809 for the docker run command
810 - imageArgs: A string containing command line arguments for the
811 command run by docker
812 """
813 try:
814 cmdStr = "docker run --name %s %s %s %s" % ( containerName,
815 options if options else "",
816 image,
817 imageArgs )
Jon Hall06fd0df2021-01-25 15:50:06 -0800818 if background:
819 cmdStr += " &"
Jon Hall3c0114c2020-08-11 15:07:42 -0700820 main.log.info( self.name + ": sending: " + cmdStr )
821 self.handle.sendline( cmdStr)
822 i = self.handle.expect( [ self.prompt,
823 "Error response from daemon",
824 pexpect.TIMEOUT ], 120 )
825 if i == 0:
826 return main.TRUE
827 else:
828 output = self.handle.before
829 main.log.debug( self.name + ": " + output )
830 main.log.error( self.name + ": Error running docker image" )
831 if i == 1:
832 output += self.handle.after
833 self.handle.expect( self.prompt )
834 output += self.handle.before + self.handle.after
835 main.log.debug( self.name + ": " + output )
836 return main.FALSE
837 except pexpect.EOF:
838 main.log.error( self.name + ": EOF exception found" )
839 main.log.error( self.name + ": " + self.handle.before )
840 return main.FALSE
841 except Exception:
842 main.log.exception( self.name + ": Uncaught exception!" )
843 return main.FALSE
844
845 def dockerAttach( self, containerName, dockerPrompt="" ):
846 """
847 Attach to a docker image
848 Required Arguments:
849 - containerName: The name of the container to attach to
850 Optional Arguments:
851 - dockerPrompt: a regex for matching the docker shell prompt
852 """
853 try:
854 if dockerPrompt:
855 self.dockerPrompt = dockerPrompt
856 cmdStr = "docker attach %s" % containerName
857 main.log.info( self.name + ": sending: " + cmdStr )
858 self.handle.sendline( cmdStr)
859 i = self.handle.expect( [ self.dockerPrompt,
860 "Error response from daemon",
861 pexpect.TIMEOUT ] )
862 if i == 0:
863 self.inDocker = True
864 return main.TRUE
865 else:
866 main.log.error( self.name + ": Error connecting to docker container" )
867 output = self.handle.before + str( self.handle.after )
868 if i == 1:
869 self.handle.expect( self.prompt )
870 output += self.handle.before + str( self.handle.after )
871 main.log.debug( self.name + ": " + output )
872 return main.FALSE
873 except pexpect.EOF:
874 main.log.error( self.name + ": EOF exception found" )
875 main.log.error( self.name + ": " + self.handle.before )
876 return main.FALSE
877 except AttributeError as e:
878 main.log.exception( self.name + ": AttributeError - " + str( e ) )
879 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
880 main.cleanup()
881 main.exit()
882 except Exception:
883 main.log.exception( self.name + ": Uncaught exception!" )
884 return main.FALSE
885
886 def dockerExec( self, containerName, command="/bin/bash", options="-it", dockerPrompt="" ):
887 """
888 Attach to a docker image
889 Required Arguments:
890 - containerName: The name of the container to attach to
891 Optional Arguments:
892 - command: Command to run in the docker container
893 - options: Docker exec options
894 - dockerPrompt: a regex for matching the docker shell prompt
895 """
896 try:
897 if dockerPrompt:
898 self.dockerPrompt = dockerPrompt
899 cmdStr = "docker exec %s %s %s" % ( options, containerName, command )
900 main.log.info( self.name + ": sending: " + cmdStr )
901 self.handle.sendline( cmdStr)
902 i = self.handle.expect( [ self.dockerPrompt,
903 "Error response from daemon",
904 pexpect.TIMEOUT ] )
905 if i == 0:
906 self.inDocker = True
907 return main.TRUE
908 else:
909 main.log.error( self.name + ": Error connecting to docker container" )
910 output = self.handle.before + str( self.handle.after )
911 if i == 1:
912 self.handle.expect( self.prompt )
913 output += self.handle.before + str( self.handle.after )
914 main.log.debug( self.name + ": " + output )
915 return main.FALSE
916 except pexpect.EOF:
917 main.log.error( self.name + ": EOF exception found" )
918 main.log.error( self.name + ": " + self.handle.before )
919 return main.FALSE
920 except AttributeError as e:
921 main.log.exception( self.name + ": AttributeError - " + str( e ) )
922 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
923 main.cleanup()
924 main.exit()
925 except Exception:
926 main.log.exception( self.name + ": Uncaught exception!" )
927 return main.FALSE
928
929 def dockerCp( self, containerName, dockerPath, hostPath, direction="from" ):
930 """
931 Copy a file from/to a docker container to the host
932 Required Arguments:
933 - containerName: The name of the container to copy from/to
934 - dockerPath: the path in the container to copy from/to
935 - hostPath: the path on the host to copy to/from
936 Optional Arguments:
937 - direction: Choose whether to copy "from" the container or "to" the container
938 """
939 try:
940 cmdStr = "docker cp "
941 if direction == "from":
942 cmdStr += "%s:%s %s" % ( containerName, dockerPath, hostPath )
943 elif direction == "to":
944 cmdStr += "%s %s:%s" % ( hostPath, containerName, dockerPath )
945 main.log.info( self.name + ": sending: " + cmdStr )
946 self.handle.sendline( cmdStr)
947 i = self.handle.expect( [ self.prompt,
948 "Error",
949 pexpect.TIMEOUT ] )
950 if i == 0:
951 retValue = main.TRUE
952 else:
953 main.log.error( self.name + ": Error in docker cp" )
954 output = self.handle.before + str( self.handle.after )
955 if i == 1:
956 self.handle.expect( self.prompt )
957 output += self.handle.before + str( self.handle.after )
958 main.log.debug( self.name + ": " + output )
959 retValue = main.FALSE
960 return retValue
961 except pexpect.EOF:
962 main.log.error( self.name + ": EOF exception found" )
963 main.log.error( self.name + ": " + self.handle.before )
964 return main.FALSE
965 except AttributeError as e:
966 main.log.exception( self.name + ": AttributeError - " + str( e ) )
967 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
968 main.cleanup()
969 main.exit()
970 except Exception:
971 main.log.exception( self.name + ": Uncaught exception!" )
972 return main.FALSE
973
974 def dockerDisconnect( self ):
975 """
976 Send ctrl-c, ctrl-d to session, which should close and exit the
977 attached docker session. This will likely exit the running program
978 in the container and also stop the container.
979 """
980 try:
981 cmdStr = "\x03"
982 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
983 self.handle.send( cmdStr)
984 cmdStr = "\x04"
985 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
986 self.handle.send( cmdStr)
987 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
988 if i == 0:
989 self.inDocker = False
990 return main.TRUE
991 else:
992 main.log.error( self.name + ": Error disconnecting from docker image" )
993 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
994 return main.FALSE
995 except pexpect.EOF:
996 main.log.error( self.name + ": EOF exception found" )
997 main.log.error( self.name + ": " + self.handle.before )
998 return main.FALSE
999 except Exception:
1000 main.log.exception( self.name + ": Uncaught exception!" )
1001 return main.FALSE
Jon Hall06fd0df2021-01-25 15:50:06 -08001002
1003# TODO: How is this different from exitFromCmd used elsewhere?
1004 def exitFromProcess( self ):
1005 """
1006 Send ctrl-c, which should close and exit the program
1007 """
1008 try:
1009 cmdStr = "\x03"
1010 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1011 self.handle.send( cmdStr)
1012 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
1013 if i == 0:
1014 return main.TRUE
1015 else:
1016 main.log.error( self.name + ": Error exiting process" )
1017 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1018 return main.FALSE
1019 except pexpect.EOF:
1020 main.log.error( self.name + ": EOF exception found" )
1021 main.log.error( self.name + ": " + self.handle.before )
1022 return main.FALSE
1023 except Exception:
1024 main.log.exception( self.name + ": Uncaught exception!" )
1025 return main.FALSE
1026
1027 def preDisconnect( self ):
1028 """
1029 A Stub for a function that will be called before disconnect.
1030 This can be set if for instance, the shell is running a program
1031 and needs to exit the program before disconnecting from the component
1032 """
1033 print "preDisconnect"
1034 return main.TRUE
1035
Jon Hall22a3bcf2021-07-23 11:40:11 -07001036 def kubectlGetPodNames( self, kubeconfig=None, namespace=None, app=None, name=None,
1037 nodeName=None, status=None ):
Jon Hall06fd0df2021-01-25 15:50:06 -08001038 """
1039 Use kubectl to get the names of pods
1040 Optional Arguments:
1041 - kubeconfig: The path to a kubeconfig file
1042 - namespace: The namespace to search in
1043 - app: Get pods belonging to a specific app
1044 - name: Get pods with a specific name label
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001045 - nodeName: Get pods on a specific node
Jon Hall22a3bcf2021-07-23 11:40:11 -07001046 - status: Get pods with the specified Status
Jon Hall06fd0df2021-01-25 15:50:06 -08001047 Returns a list containing the names of the pods or
1048 main.FALSE on Error
1049 """
1050
1051 try:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001052 self.handle.sendline( "" )
1053 self.handle.expect( self.prompt )
1054 main.log.debug( self.handle.before + self.handle.after )
1055 cmdStr = "kubectl %s %s get pods %s %s %s %s --output=jsonpath='{.items..metadata.name}{\"\\n\"}'" % (
Jon Hall06fd0df2021-01-25 15:50:06 -08001056 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1057 "-n %s" % namespace if namespace else "",
1058 "-l app=%s" % app if app else "",
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001059 "-l name=%s" % name if name else "",
Jon Hall22a3bcf2021-07-23 11:40:11 -07001060 "--field-selector=spec.nodeName=%s" % nodeName if nodeName else "",
1061 "--field-selector=status.phase=%s" % status if status else "" )
Jon Hall06fd0df2021-01-25 15:50:06 -08001062 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1063 self.handle.sendline( cmdStr )
Jon Hall2941fce2021-04-13 10:38:23 -07001064 i = self.handle.expect( [ "not found", "error", "The connection to the server", "Unable to find", "No resources found", self.prompt ] )
1065 if i == 4:
1066 # Command worked, but returned no pods
1067 output = self.handle.before + self.handle.after
1068 main.log.warn( self.name + ": " + output )
1069 return []
1070 elif i == 5:
1071 # Command returned pods
Jon Hall06fd0df2021-01-25 15:50:06 -08001072 output = self.handle.before + self.handle.after
1073 names = output.split( '\r\n' )[1].split()
1074 return names
1075 else:
Jon Hall2941fce2021-04-13 10:38:23 -07001076 # Some error occured
Jon Hall06fd0df2021-01-25 15:50:06 -08001077 main.log.error( self.name + ": Error executing command" )
1078 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1079 return main.FALSE
1080 except pexpect.EOF:
1081 main.log.error( self.name + ": EOF exception found" )
1082 main.log.error( self.name + ": " + self.handle.before )
1083 return main.FALSE
1084 except pexpect.TIMEOUT:
1085 main.log.exception( self.name + ": TIMEOUT exception found" )
1086 main.log.error( self.name + ": " + self.handle.before )
1087 return main.FALSE
1088 except Exception:
1089 main.log.exception( self.name + ": Uncaught exception!" )
1090 return main.FALSE
1091
1092 def kubectlDescribe( self, describeString, dstPath, kubeconfig=None, namespace=None ):
1093 """
1094 Use kubectl to get the logs from a pod
1095 Required Arguments:
1096 - describeString: The string passed to the cli. Example: "pods"
1097 - dstPath: The location to save the logs to
1098 Optional Arguments:
1099 - kubeconfig: The path to a kubeconfig file
1100 - namespace: The namespace to search in
1101 Returns main.TRUE or
1102 main.FALSE on Error
1103 """
1104
1105 try:
1106 cmdStr = "kubectl %s %s describe %s > %s " % (
1107 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1108 "-n %s" % namespace if namespace else "",
1109 describeString,
1110 dstPath )
1111 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1112 self.handle.sendline( cmdStr )
1113 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1114 if i == 3:
1115 main.log.debug( self.name + ": " + self.handle.before )
1116 return main.TRUE
1117 else:
1118 main.log.error( self.name + ": Error executing command" )
1119 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1120 return main.FALSE
1121 except pexpect.EOF:
1122 main.log.error( self.name + ": EOF exception found" )
1123 main.log.error( self.name + ": " + self.handle.before )
1124 return main.FALSE
1125 except pexpect.TIMEOUT:
1126 main.log.exception( self.name + ": TIMEOUT exception found" )
1127 main.log.error( self.name + ": " + self.handle.before )
1128 return main.FALSE
1129 except Exception:
1130 main.log.exception( self.name + ": Uncaught exception!" )
1131 return main.FALSE
1132
1133 def kubectlPodNodes( self, dstPath=None, kubeconfig=None, namespace=None ):
1134 """
1135 Use kubectl to get the logs from a pod
1136 Optional Arguments:
1137 - dstPath: The location to save the logs to
1138 - kubeconfig: The path to a kubeconfig file
1139 - namespace: The namespace to search in
1140 Returns main.TRUE if dstPath is given, else the output of the command or
1141 main.FALSE on Error
1142 """
1143
1144 try:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001145 self.handle.sendline( "" )
1146 self.handle.expect( self.prompt )
1147 main.log.debug( self.handle.before + self.handle.after )
Jon Hall50a00012021-03-08 11:06:11 -08001148 cmdStr = "kubectl %s %s get pods -o wide %s " % (
Jon Hall06fd0df2021-01-25 15:50:06 -08001149 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1150 "-n %s" % namespace if namespace else "",
1151 " > %s" % dstPath if dstPath else "" )
1152 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1153 self.handle.sendline( cmdStr )
1154 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1155 if i == 3:
1156 output = self.handle.before
1157 main.log.debug( self.name + ": " + output )
1158 return output if dstPath else main.TRUE
1159 else:
1160 main.log.error( self.name + ": Error executing command" )
1161 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1162 return main.FALSE
1163 except pexpect.EOF:
1164 main.log.error( self.name + ": EOF exception found" )
1165 main.log.error( self.name + ": " + self.handle.before )
1166 return main.FALSE
1167 except pexpect.TIMEOUT:
1168 main.log.exception( self.name + ": TIMEOUT exception found" )
1169 main.log.error( self.name + ": " + self.handle.before )
1170 return main.FALSE
1171 except Exception:
1172 main.log.exception( self.name + ": Uncaught exception!" )
1173 return main.FALSE
1174
Jon Halla7b27e62021-06-29 12:13:51 -07001175 def sternLogs( self, podString, dstPath, kubeconfig=None, namespace=None, since='1h', wait=60 ):
1176 """
1177 Use stern to get the logs from a pod
1178 Required Arguments:
1179 - podString: The name of the pod or partial name of the pods to get the logs of
1180 - dstPath: The location to save the logs to
1181 Optional Arguments:
1182 - kubeconfig: The path to a kubeconfig file
1183 - namespace: The namespace to search in
1184 - since: Return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to 1h
1185 - wait: How long to wait, in seconds, before killing the process. Stern does not currently
1186 support a way to exit if cought up to present time. Defaults to 60 seconds
1187 Returns main.TRUE or
1188 main.FALSE on Error
1189 """
1190 import time
1191 try:
1192 cmdStr = "stern %s %s %s %s > %s " % (
1193 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1194 "-n %s" % namespace if namespace else "",
1195 "--since %s" % since if since else "",
1196 podString,
1197 dstPath )
1198 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1199 self.handle.sendline( cmdStr )
Jon Hall22a3bcf2021-07-23 11:40:11 -07001200 if int( wait ) >= 0:
1201 time.sleep( int( wait ) )
1202 self.handle.send( '\x03' ) # CTRL-C
1203 i = self.handle.expect( [ "not found", "Error: ", "The connection to the server", self.prompt ] )
1204 if i == 3:
1205 main.log.debug( self.name + ": " + self.handle.before )
1206 return main.TRUE
1207 else:
1208 main.log.error( self.name + ": Error executing command" )
1209 response = self.handle.before + str( self.handle.after )
1210 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ], timeout=5 )
1211 response += self.handle.before + str( self.handle.after )
1212 main.log.debug( self.name + ": " + response )
1213 return main.FALSE
Jon Halla7b27e62021-06-29 12:13:51 -07001214 else:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001215 self.preDisconnect = self.exitFromProcess
1216 return main.TRUE
Jon Halla7b27e62021-06-29 12:13:51 -07001217 except pexpect.EOF:
1218 main.log.error( self.name + ": EOF exception found" )
1219 main.log.error( self.name + ": " + self.handle.before )
1220 return main.FALSE
1221 except pexpect.TIMEOUT:
1222 main.log.exception( self.name + ": TIMEOUT exception found" )
1223 main.log.error( self.name + ": " + self.handle.before )
1224 return main.FALSE
1225 except Exception:
1226 main.log.exception( self.name + ": Uncaught exception!" )
1227 return main.FALSE
1228
Jon Hall06fd0df2021-01-25 15:50:06 -08001229 def kubectlLogs( self, podName, dstPath, kubeconfig=None, namespace=None, timeout=240 ):
1230 """
1231 Use kubectl to get the logs from a pod
1232 Required Arguments:
1233 - podName: The name of the pod to get the logs of
1234 - dstPath: The location to save the logs to
1235 Optional Arguments:
1236 - kubeconfig: The path to a kubeconfig file
1237 - namespace: The namespace to search in
1238 - timeout: Timeout for command to return. The longer the logs, the longer it will take to fetch them.
1239 Returns main.TRUE or
1240 main.FALSE on Error
1241 """
1242
1243 try:
1244 cmdStr = "kubectl %s %s logs %s > %s " % (
1245 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1246 "-n %s" % namespace if namespace else "",
1247 podName,
1248 dstPath )
1249 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1250 self.handle.sendline( cmdStr )
1251 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ], timeout=timeout )
1252 if i == 3:
1253 main.log.debug( self.name + ": " + self.handle.before )
1254 return main.TRUE
1255 else:
1256 main.log.error( self.name + ": Error executing command" )
1257 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1258 return main.FALSE
1259 except pexpect.EOF:
1260 main.log.error( self.name + ": EOF exception found" )
1261 main.log.error( self.name + ": " + self.handle.before )
1262 return main.FALSE
1263 except pexpect.TIMEOUT:
1264 main.log.exception( self.name + ": TIMEOUT exception found" )
1265 main.log.error( self.name + ": " + self.handle.before )
1266 return main.FALSE
1267 except Exception:
1268 main.log.exception( self.name + ": Uncaught exception!" )
1269 return main.FALSE
1270
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001271 def kubectlCp( self, podName, srcPath, dstPath, kubeconfig=None, namespace=None, timeout=240 ):
1272 """
1273 Use kubectl to get a file from a pod
1274 Required Arguments:
1275 - podName: The name of the pod to get the logs of
1276 - srcPath: The file to copy from the pod
1277 - dstPath: The location to save the file to locally
1278 Optional Arguments:
1279 - kubeconfig: The path to a kubeconfig file
1280 - namespace: The namespace to search in
1281 - timeout: Timeout for command to return. The longer the logs, the longer it will take to fetch them.
1282 Returns main.TRUE or
1283 main.FALSE on Error
1284 """
1285
1286 try:
1287 cmdStr = "kubectl %s %s cp %s:%s %s" % (
1288 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1289 "-n %s" % namespace if namespace else "",
1290 podName,
1291 srcPath,
1292 dstPath )
1293 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1294 self.handle.sendline( cmdStr )
1295 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ], timeout=timeout )
1296 if i == 3:
1297 main.log.debug( self.name + ": " + self.handle.before )
1298 return main.TRUE
1299 else:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001300 output = self.handle.before + str( self.handle.after )
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001301 main.log.error( self.name + ": Error executing command" )
Jon Hall22a3bcf2021-07-23 11:40:11 -07001302 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
1303 output += self.handle.before + str( self.handle.after )
1304 main.log.debug( self.name + ": " + output )
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001305 return main.FALSE
1306 except pexpect.EOF:
1307 main.log.error( self.name + ": EOF exception found" )
1308 main.log.error( self.name + ": " + self.handle.before )
1309 return main.FALSE
1310 except pexpect.TIMEOUT:
1311 main.log.exception( self.name + ": TIMEOUT exception found" )
1312 main.log.error( self.name + ": " + self.handle.before )
1313 return main.FALSE
1314 except Exception:
1315 main.log.exception( self.name + ": Uncaught exception!" )
1316 return main.FALSE
1317
Jon Hall06fd0df2021-01-25 15:50:06 -08001318 def kubectlPortForward( self, podName, portsList, kubeconfig=None, namespace=None, ):
1319 """
1320 Use kubectl to setup port forwarding from the local machine to the kubernetes pod
1321
1322 Note: This command does not return until the port forwarding session is ended.
1323
1324 Required Arguments:
1325 - podName: The name of the pod as a string
1326 - portsList: The list of ports to forward, as a string. see kubectl help for details
1327 Optional Arguments:
1328 - kubeconfig: The path to a kubeconfig file
1329 - namespace: The namespace to search in
1330 - app: Get pods belonging to a specific app
1331 Returns a list containing the names of the pods or
1332 main.FALSE on Error
1333
1334
1335 """
1336 try:
1337 cmdStr = "kubectl %s %s port-forward pod/%s %s" % (
1338 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1339 "-n %s" % namespace if namespace else "",
1340 podName,
1341 portsList )
1342 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1343 self.handle.sendline( cmdStr )
1344 i = self.handle.expect( [ "not found", "error", "closed/timedout",
1345 self.prompt, "The connection to the server", "Forwarding from" ] )
1346 # NOTE: This won't clear the buffer entirely, and each time the port forward
1347 # is used, another line will be added to the buffer. We need to make
1348 # sure we clear the buffer before using this component again.
1349
1350 if i == 5:
1351 # Setup preDisconnect function
1352 self.preDisconnect = self.exitFromProcess
1353 return main.TRUE
1354 else:
1355 main.log.error( self.name + ": Error executing command" )
1356 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1357 return main.FALSE
1358 except pexpect.EOF:
1359 main.log.error( self.name + ": EOF exception found" )
1360 main.log.error( self.name + ": " + self.handle.before )
1361 return main.FALSE
1362 except pexpect.TIMEOUT:
1363 main.log.exception( self.name + ": TIMEOUT exception found" )
1364 main.log.error( self.name + ": " + self.handle.before )
1365 return main.FALSE
1366 except Exception:
1367 main.log.exception( self.name + ": Uncaught exception!" )
1368 return main.FALSE