blob: 4f081ea8d1656e8a177b8186b72c0ea3ba18c7a4 [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 Halla16b4db2021-10-20 14:11:59 -070038 self.portForwardList = None
Jon Hallca319892017-06-15 15:25:22 -070039
Jeremy Ronquillo82705492017-10-18 14:19:55 -070040 def checkPrompt( self ):
Devin Limdc78e202017-06-09 18:30:07 -070041 for key in self.options:
Jeremy Ronquillo82705492017-10-18 14:19:55 -070042 if key == "prompt" and self.options[ 'prompt' ] is not None:
43 self.prompt = self.options[ 'prompt' ]
Devin Limdc78e202017-06-09 18:30:07 -070044 break
kelvin8ec71442015-01-15 16:57:00 -080045
46 def connect( self, **connectargs ):
47 """
adminbae64d82013-08-01 10:50:15 -070048 Connection will establish to the remote host using ssh.
49 It will take user_name ,ip_address and password as arguments<br>
kelvin8ec71442015-01-15 16:57:00 -080050 and will return the handle.
51 """
Jon Hall06fd0df2021-01-25 15:50:06 -080052 self.shell = "/bin/bash -l"
adminbae64d82013-08-01 10:50:15 -070053 for key in connectargs:
kelvin8ec71442015-01-15 16:57:00 -080054 vars( self )[ key ] = connectargs[ key ]
Devin Limdc78e202017-06-09 18:30:07 -070055 self.checkPrompt()
adminbae64d82013-08-01 10:50:15 -070056
kelvin8ec71442015-01-15 16:57:00 -080057 connect_result = super( CLI, self ).connect()
adminbae64d82013-08-01 10:50:15 -070058 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -080059 refused = "ssh: connect to host " + \
60 self.ip_address + " port 22: Connection refused"
Jon Halla16b4db2021-10-20 14:11:59 -070061 ssh_options = "-t -X -A -o ServerAliveInterval=50 -o ServerAliveCountMax=1000 -o TCPKeepAlive=yes"
Jon Hall06fd0df2021-01-25 15:50:06 -080062 ssh_destination = self.user_name + "@" + self.ip_address
63 envVars = { "TERM": "vt100" }
64 # TODO: Add option to specify which shell/command to use
65 jump_host = main.componentDictionary[ self.name ].get( 'jump_host' )
adminbae64d82013-08-01 10:50:15 -070066 if self.port:
Jon Hall06fd0df2021-01-25 15:50:06 -080067 ssh_option += " -p " + self.port
68 if jump_host:
69 jump_host = main.componentDictionary.get( jump_host )
70 ssh_options += " -J %s@%s" % ( jump_host.get( 'user' ), jump_host.get( 'host' ) )
71 ssh_auth = os.getenv('SSH_AUTH_SOCK')
72 if ssh_auth:
73 envVars[ 'SSH_AUTH_SOCK' ] = ssh_auth
74 self.handle = pexpect.spawn(
75 "ssh %s %s %s" % ( ssh_options, ssh_destination, self.shell ),
76 env=envVars,
77 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" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800122 main.log.debug( self.handle.before )
kelvin8ec71442015-01-15 16:57:00 -0800123 return main.FALSE
124 elif i == 3: # timeout
125 main.log.error(
126 "No route to the Host " +
127 self.user_name +
128 "@" +
129 self.ip_address )
Jon Hall06fd0df2021-01-25 15:50:06 -0800130 main.log.debug( self.handle.before )
kelvin8ec71442015-01-15 16:57:00 -0800131 return main.FALSE
132 elif i == 4:
133 main.log.error(
134 "ssh: connect to host " +
135 self.ip_address +
136 " port 22: Connection refused" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800137 main.log.debug( self.handle.before )
kelvin8ec71442015-01-15 16:57:00 -0800138 return main.FALSE
Jon Halld9066132018-03-01 14:52:53 -0800139 elif i == 6: # Incorrect Password
Jon Hall3c0114c2020-08-11 15:07:42 -0700140 main.log.error( self.name + ": Incorrect Password" )
Jon Hall06fd0df2021-01-25 15:50:06 -0800141 main.log.debug( self.handle.before )
Jon Halld9066132018-03-01 14:52:53 -0800142 return main.FALSE
143 elif i == 7: # Prompt
Jon Hall3c0114c2020-08-11 15:07:42 -0700144 main.log.info( self.name + ": Password not required logged in" )
adminbae64d82013-08-01 10:50:15 -0700145
kelvin8ec71442015-01-15 16:57:00 -0800146 self.handle.sendline( "" )
Devin Limdc78e202017-06-09 18:30:07 -0700147 self.handle.expect( self.prompt )
Jon Hall7676c662021-11-17 14:32:06 -0800148
149 # disable bracketed paste mode which is enabled by default on newer versions of bash/readline
150 self.handle.sendline( "bind 'set enable-bracketed-paste off'" )
151 self.handle.expect( self.prompt )
152
Jeremy Ronquillo0f2008a2017-06-23 15:32:51 -0700153 self.handle.sendline( "cd" )
154 self.handle.expect( self.prompt )
adminbae64d82013-08-01 10:50:15 -0700155 return self.handle
156
kelvin8ec71442015-01-15 16:57:00 -0800157 def disconnect( self ):
Jon Hall06fd0df2021-01-25 15:50:06 -0800158 result = self.preDisconnect()
Jon Halla4a79312022-01-25 17:16:53 -0800159 result = super( CLI, self ).disconnect( )
adminbae64d82013-08-01 10:50:15 -0700160 result = main.TRUE
Jon Hall3c0114c2020-08-11 15:07:42 -0700161
162 def Prompt( self ):
163 """
164 Returns the prompt to expect depending on what program we are in
165 """
166 return self.prompt if not self.inDocker else self.dockerPrompt
kelvin8ec71442015-01-15 16:57:00 -0800167
168 def execute( self, **execparams ):
169 """
adminbae64d82013-08-01 10:50:15 -0700170 It facilitates the command line execution of a given command. It has arguments as :
171 cmd => represents command to be executed,
172 prompt => represents expect command prompt or output,
173 timeout => timeout for command execution,
174 more => to provide a key press if it is on.
You Wang7d14d642019-01-23 15:10:08 -0800175 logCmd => log the command executed if True
adminbae64d82013-08-01 10:50:15 -0700176
177 It will return output of command exection.
kelvin8ec71442015-01-15 16:57:00 -0800178 """
179 result = super( CLI, self ).execute( self )
adminaef00552014-05-08 09:18:36 -0700180 defaultPrompt = '.*[$>\#]'
Jon Hall3b489db2015-10-05 14:38:37 -0700181 args = utilities.parse_args( [ "CMD",
182 "TIMEOUT",
183 "PROMPT",
You Wang7d14d642019-01-23 15:10:08 -0800184 "MORE",
185 "LOGCMD" ],
Jon Hall3b489db2015-10-05 14:38:37 -0700186 **execparams )
kelvin8ec71442015-01-15 16:57:00 -0800187
188 expectPrompt = args[ "PROMPT" ] if args[ "PROMPT" ] else defaultPrompt
adminbae64d82013-08-01 10:50:15 -0700189 self.LASTRSP = ""
kelvin8ec71442015-01-15 16:57:00 -0800190 timeoutVar = args[ "TIMEOUT" ] if args[ "TIMEOUT" ] else 10
adminbae64d82013-08-01 10:50:15 -0700191 cmd = ''
kelvin8ec71442015-01-15 16:57:00 -0800192 if args[ "CMD" ]:
193 cmd = args[ "CMD" ]
194 else:
adminbae64d82013-08-01 10:50:15 -0700195 return 0
kelvin8ec71442015-01-15 16:57:00 -0800196 if args[ "MORE" ] is None:
197 args[ "MORE" ] = " "
198 self.handle.sendline( cmd )
adminbae64d82013-08-01 10:50:15 -0700199 self.lastCommand = cmd
Jon Hall3b489db2015-10-05 14:38:37 -0700200 index = self.handle.expect( [ expectPrompt,
201 "--More--",
202 'Command not found.',
203 pexpect.TIMEOUT,
204 "^:$" ],
205 timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700206 if index == 0:
kelvin8ec71442015-01-15 16:57:00 -0800207 self.LASTRSP = self.LASTRSP + \
208 self.handle.before + self.handle.after
You Wang7d14d642019-01-23 15:10:08 -0800209 if not args[ "LOGCMD" ] is False:
Jon Hall3c0114c2020-08-11 15:07:42 -0700210 main.log.info( self.name + ": Executed :" + str( cmd ) +
You Wang7d14d642019-01-23 15:10:08 -0800211 " \t\t Expected Prompt '" + str( expectPrompt ) +
212 "' Found" )
adminbae64d82013-08-01 10:50:15 -0700213 elif index == 1:
214 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800215 self.handle.send( args[ "MORE" ] )
216 main.log.info(
217 "Found More screen to go , Sending a key to proceed" )
218 indexMore = self.handle.expect(
219 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700220 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800221 main.log.info(
222 "Found anoother More screen to go , Sending a key to proceed" )
223 self.handle.send( args[ "MORE" ] )
224 indexMore = self.handle.expect(
225 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700226 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800227 elif index == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700228 main.log.error( self.name + ": Command not found" )
adminbae64d82013-08-01 10:50:15 -0700229 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800230 elif index == 3:
Jon Hall3c0114c2020-08-11 15:07:42 -0700231 main.log.error( self.name + ": Expected Prompt not found, Time Out!!" )
kelvin8ec71442015-01-15 16:57:00 -0800232 main.log.error( expectPrompt )
Jon Hall3b489db2015-10-05 14:38:37 -0700233 self.LASTRSP = self.LASTRSP + self.handle.before
234 return self.LASTRSP
adminbae64d82013-08-01 10:50:15 -0700235 elif index == 4:
236 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800237 # self.handle.send( args[ "MORE" ] )
238 self.handle.sendcontrol( "D" )
239 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700240 "Found More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800241 indexMore = self.handle.expect(
242 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700243 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800244 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700245 "Found another More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800246 self.handle.sendcontrol( "D" )
247 indexMore = self.handle.expect(
248 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700249 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800250 main.last_response = self.remove_contol_chars( self.LASTRSP )
adminbae64d82013-08-01 10:50:15 -0700251 return self.LASTRSP
kelvin8ec71442015-01-15 16:57:00 -0800252
253 def remove_contol_chars( self, response ):
254 # 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 ) )
255 # response = re.sub( RE_XML_ILLEGAL, "\n", response )
256 response = re.sub( r"[\x01-\x1F\x7F]", "", response )
257 # response = re.sub( r"\[\d+\;1H", "\n", response )
258 response = re.sub( r"\[\d+\;\d+H", "", response )
adminbae64d82013-08-01 10:50:15 -0700259 return response
adminbae64d82013-08-01 10:50:15 -0700260
kelvin8ec71442015-01-15 16:57:00 -0800261 def runAsSudoUser( self, handle, pwd, default ):
262
263 i = handle.expect( [ ".ssword:*", default, pexpect.EOF ] )
264 if i == 0:
265 handle.sendline( pwd )
Jon Hall5ec6b1b2015-09-17 18:20:14 -0700266 handle.sendline( "\n" )
kelvin8ec71442015-01-15 16:57:00 -0800267
268 if i == 1:
269 handle.expect( default )
270
271 if i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700272 main.log.error( self.name + ": Unable to run as Sudo user" )
kelvin8ec71442015-01-15 16:57:00 -0800273
adminbae64d82013-08-01 10:50:15 -0700274 return handle
adminbae64d82013-08-01 10:50:15 -0700275
kelvin8ec71442015-01-15 16:57:00 -0800276 def onfail( self ):
277 if 'onfail' in main.componentDictionary[ self.name ]:
278 commandList = main.componentDictionary[
279 self.name ][ 'onfail' ].split( "," )
280 for command in commandList:
281 response = self.execute(
282 cmd=command,
283 prompt="(.*)",
284 timeout=120 )
adminbae64d82013-08-01 10:50:15 -0700285
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700286 def secureCopy( self, userName, ipAddress, filePath, dstPath, pwd="",
Jon Hall22a3bcf2021-07-23 11:40:11 -0700287 direction="from", options="", timeout=120 ):
kelvin8ec71442015-01-15 16:57:00 -0800288 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700289 Definition:
290 Execute scp command in linux to copy to/from a remote host
291 Required:
292 str userName - User name of the remote host
293 str ipAddress - IP address of the remote host
294 str filePath - File path including the file it self
295 str dstPath - Destination path
296 Optional:
297 str pwd - Password of the host
298 str direction - Direction of the scp, default to "from" which means
299 copy "from" the remote machine to local machine,
300 while "to" means copy "to" the remote machine from
301 local machine
kelvin8ec71442015-01-15 16:57:00 -0800302 """
Jon Hall669bc862021-03-09 12:24:44 -0800303 returnVal = main.FALSE
adminbae64d82013-08-01 10:50:15 -0700304 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -0800305 refused = "ssh: connect to host " + \
Jon Hall547e0582015-09-21 17:35:40 -0700306 ipAddress + " port 22: Connection refused"
Jon Hall39570262020-11-17 12:18:19 -0800307 cmd = "scp %s " % options
Jon Hall669bc862021-03-09 12:24:44 -0800308 try:
309 self.handle.sendline( "" )
310 self.handle.expect( self.prompt, timeout=5 )
311 except pexpect.TIMEOUT:
312 main.log.error( "%s: Component not ready for input" % self.name )
313 main.log.debug( "%s: %s%s" % ( self.name, self.handle.before, str( self.handle.after ) ) )
314 self.handle.send( "\x03" ) # CTRL-C
315 self.handle.expect( self.prompt, timeout=5 )
acsmars07f9d392015-07-15 10:30:58 -0700316
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700317 if direction == "from":
Jon Hall39570262020-11-17 12:18:19 -0800318 cmd = cmd + str( userName ) + '@' + str( ipAddress ) + ':' + \
Jon Hall547e0582015-09-21 17:35:40 -0700319 str( filePath ) + ' ' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700320 elif direction == "to":
Jon Hall39570262020-11-17 12:18:19 -0800321 cmd = cmd + str( filePath ) + ' ' + str( userName ) + \
Jon Hall547e0582015-09-21 17:35:40 -0700322 '@' + str( ipAddress ) + ':' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700323 else:
324 main.log.debug( "Wrong direction using secure copy command!" )
325 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800326
Jon Hall3c0114c2020-08-11 15:07:42 -0700327 main.log.info( self.name + ": Sending: " + cmd )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700328 self.handle.sendline( cmd )
Jon Hall547e0582015-09-21 17:35:40 -0700329 i = 0
Jon Hall66ce22f2021-06-30 14:57:40 -0700330 hit = False
Jon Hall669bc862021-03-09 12:24:44 -0800331 while i <= 6 :
Jon Hall547e0582015-09-21 17:35:40 -0700332 i = self.handle.expect( [
333 ssh_newkey,
334 'password:',
335 "100%",
336 refused,
337 "No such file or directory",
Jon Hall53c5e662016-04-13 16:06:56 -0700338 "Permission denied",
Devin Limdc78e202017-06-09 18:30:07 -0700339 self.prompt,
Jon Hall547e0582015-09-21 17:35:40 -0700340 pexpect.EOF,
341 pexpect.TIMEOUT ],
Jon Hall669bc862021-03-09 12:24:44 -0800342 timeout=timeout )
Jon Hall547e0582015-09-21 17:35:40 -0700343 if i == 0: # ask for ssh key confirmation
Jon Hall66ce22f2021-06-30 14:57:40 -0700344 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700345 main.log.info( self.name + ": ssh key confirmation received, sending yes" )
Jon Hall547e0582015-09-21 17:35:40 -0700346 self.handle.sendline( 'yes' )
347 elif i == 1: # Asked for ssh password
Jon Hall66ce22f2021-06-30 14:57:40 -0700348 hit = True
Jon Hall669bc862021-03-09 12:24:44 -0800349 timeout = 120
Jon Hall3c0114c2020-08-11 15:07:42 -0700350 main.log.info( self.name + ": ssh connection asked for password, gave password" )
Jon Hall547e0582015-09-21 17:35:40 -0700351 self.handle.sendline( pwd )
352 elif i == 2: # File finished transfering
Jon Hall66ce22f2021-06-30 14:57:40 -0700353 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700354 main.log.info( self.name + ": Secure copy successful" )
Jon Hall669bc862021-03-09 12:24:44 -0800355 timeout = 10
Jon Hall547e0582015-09-21 17:35:40 -0700356 returnVal = main.TRUE
357 elif i == 3: # Connection refused
Jon Hall66ce22f2021-06-30 14:57:40 -0700358 hit = True
Jon Hall547e0582015-09-21 17:35:40 -0700359 main.log.error(
360 "ssh: connect to host " +
361 ipAddress +
362 " port 22: Connection refused" )
363 returnVal = main.FALSE
364 elif i == 4: # File Not found
Jon Hall66ce22f2021-06-30 14:57:40 -0700365 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700366 main.log.error( self.name + ": No such file found" )
Jon Hall39570262020-11-17 12:18:19 -0800367 main.log.debug( self.handle.before + self.handle.after )
Jon Hall547e0582015-09-21 17:35:40 -0700368 returnVal = main.FALSE
Jon Hall53c5e662016-04-13 16:06:56 -0700369 elif i == 5: # Permission denied
Jon Hall66ce22f2021-06-30 14:57:40 -0700370 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700371 main.log.error( self.name + ": Permission denied. Check folder permissions" )
Jon Hall39570262020-11-17 12:18:19 -0800372 main.log.debug( self.handle.before + self.handle.after )
Jon Hall53c5e662016-04-13 16:06:56 -0700373 returnVal = main.FALSE
374 elif i == 6: # prompt returned
Jon Hall66ce22f2021-06-30 14:57:40 -0700375 hit = True
Jon Hall669bc862021-03-09 12:24:44 -0800376 timeout = 10
377 main.log.debug( "%s: %s%s" % ( self.name, repr( self.handle.before ), repr( self.handle.after ) ) )
Jon Hall53c5e662016-04-13 16:06:56 -0700378 elif i == 7: # EOF
Jon Hall66ce22f2021-06-30 14:57:40 -0700379 hit = True
Jon Hall3c0114c2020-08-11 15:07:42 -0700380 main.log.error( self.name + ": Pexpect.EOF found!!!" )
Devin Lim44075962017-08-11 10:56:37 -0700381 main.cleanAndExit()
Jon Hall53c5e662016-04-13 16:06:56 -0700382 elif i == 8: # timeout
Jon Hall66ce22f2021-06-30 14:57:40 -0700383 if not hit:
Jon Hall669bc862021-03-09 12:24:44 -0800384 main.log.error(
385 "No route to the Host " +
386 userName +
387 "@" +
388 ipAddress )
389 return returnVal
390 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ], timeout=5 )
391 main.log.debug( "%s: %s%s" % ( self.name, repr( self.handle.before ), repr( self.handle.after ) ) )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700392 return returnVal
393
Jon Hall22a3bcf2021-07-23 11:40:11 -0700394 def scp( self, remoteHost, filePath, dstPath, direction="from", options="", timeout=120 ):
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700395 """
396 Definition:
397 Execute scp command in linux to copy to/from a remote host
398 Required:
399 * remoteHost - Test ON component to be parsed
400 str filePath - File path including the file it self
401 str dstPath - Destination path
402 Optional:
403 str direction - Direction of the scp, default to "from" which means
404 copy "from" the remote machine to local machine,
405 while "to" means copy "to" the remote machine from
406 local machine
407 """
Jon Hall06fd0df2021-01-25 15:50:06 -0800408 jump_host = main.componentDictionary[ remoteHost.name ].get( 'jump_host' )
409 if jump_host:
410 jump_host = main.componentDictionary.get( jump_host )
Jon Hall669bc862021-03-09 12:24:44 -0800411 options += " -o 'ProxyJump %s@%s' " % ( jump_host.get( 'user' ), jump_host.get( 'host' ) )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700412 return self.secureCopy( remoteHost.user_name,
413 remoteHost.ip_address,
414 filePath,
415 dstPath,
416 pwd=remoteHost.pwd,
Jon Hall39570262020-11-17 12:18:19 -0800417 direction=direction,
Jon Hall22a3bcf2021-07-23 11:40:11 -0700418 options=options,
419 timeout=timeout )
Devin Lim142b5342017-07-20 15:22:39 -0700420
421 def sshToNode( self, ipAddress, uName="sdn", pwd="rocks" ):
422 ssh_newkey = 'Are you sure you want to continue connecting'
423 refused = "ssh: connect to host " + ipAddress + " port 22: Connection refused"
424 handle = pexpect.spawn( 'ssh -X ' +
425 uName +
426 '@' +
427 ipAddress,
Jon Hall6c9e2da2018-11-06 12:01:23 -0800428 env={ "TERM": "vt100" },
Devin Lim142b5342017-07-20 15:22:39 -0700429 maxread=1000000,
430 timeout=60 )
431
432 # set tty window size
433 handle.setwinsize( 24, 250 )
434
435 i = 5
436 while i == 5:
Jon Hall4173b242017-09-12 17:04:38 -0700437 i = handle.expect( [ ssh_newkey,
438 'password:|Password:',
439 pexpect.EOF,
440 pexpect.TIMEOUT,
441 refused,
442 'teston>',
443 self.prompt ],
444 120 )
Devin Lim142b5342017-07-20 15:22:39 -0700445 if i == 0: # Accept key, then expect either a password prompt or access
Jon Hall3c0114c2020-08-11 15:07:42 -0700446 main.log.info( self.name + ": ssh key confirmation received, send yes" )
Devin Lim142b5342017-07-20 15:22:39 -0700447 handle.sendline( 'yes' )
448 i = 5 # Run the loop again
449 continue
450 if i == 1: # Password required
451 if pwd:
452 main.log.info(
Jon Hall4173b242017-09-12 17:04:38 -0700453 "ssh connection asked for password, gave password" )
Devin Lim142b5342017-07-20 15:22:39 -0700454 else:
Jon Hall3c0114c2020-08-11 15:07:42 -0700455 main.log.info( self.name + ": Server asked for password, but none was "
Devin Lim142b5342017-07-20 15:22:39 -0700456 "given in the .topo file. Trying "
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700457 "no password." )
Devin Lim142b5342017-07-20 15:22:39 -0700458 pwd = ""
459 handle.sendline( pwd )
460 j = handle.expect( [ self.prompt,
461 'password:|Password:',
462 pexpect.EOF,
463 pexpect.TIMEOUT ],
464 120 )
465 if j != 0:
Jon Hall3c0114c2020-08-11 15:07:42 -0700466 main.log.error( self.name + ": Incorrect Password" )
Devin Lim44075962017-08-11 10:56:37 -0700467 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700468 elif i == 2:
Jon Hall3c0114c2020-08-11 15:07:42 -0700469 main.log.error( self.name + ": Connection timeout" )
Devin Lim44075962017-08-11 10:56:37 -0700470 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700471 elif i == 3: # timeout
472 main.log.error(
473 "No route to the Host " +
474 uName +
475 "@" +
476 ipAddress )
Devin Lim44075962017-08-11 10:56:37 -0700477 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700478 elif i == 4:
479 main.log.error(
480 "ssh: connect to host " +
481 ipAddress +
482 " port 22: Connection refused" )
Devin Lim44075962017-08-11 10:56:37 -0700483 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700484 elif i == 6:
Jon Hall3c0114c2020-08-11 15:07:42 -0700485 main.log.info( self.name + ": Password not required logged in" )
Devin Lim142b5342017-07-20 15:22:39 -0700486
487 handle.sendline( "" )
488 handle.expect( self.prompt )
489 handle.sendline( "cd" )
490 handle.expect( self.prompt )
491
Jon Hall3c0114c2020-08-11 15:07:42 -0700492 main.log.info( self.name + ": Successfully ssh to " + ipAddress + "." )
Devin Lim142b5342017-07-20 15:22:39 -0700493 return handle
494
495 def exitFromSsh( self, handle, ipAddress ):
Devin Lim142b5342017-07-20 15:22:39 -0700496 try:
Jon Hall4f360bc2017-09-07 10:19:52 -0700497 handle.sendline( "logout" )
Devin Lim142b5342017-07-20 15:22:39 -0700498 handle.expect( "closed." )
Jon Hall3c0114c2020-08-11 15:07:42 -0700499 main.log.info( self.name + ": Successfully closed ssh connection from " + ipAddress )
Devin Lim142b5342017-07-20 15:22:39 -0700500 except pexpect.EOF:
Jon Hall3c0114c2020-08-11 15:07:42 -0700501 main.log.error( self.name + ": Failed to close the connection from " + ipAddress )
Jon Hall4f360bc2017-09-07 10:19:52 -0700502 try:
503 # check that this component handle still works
504 self.handle.sendline( "" )
505 self.handle.expect( self.prompt )
506 except pexpect.EOF:
507 main.log.error( self.handle.before )
Jon Hall3c0114c2020-08-11 15:07:42 -0700508 main.log.error( self.name + ": EOF after closing ssh connection" )
Jon Hall4173b242017-09-12 17:04:38 -0700509
510 def folderSize( self, path, size='10', unit='M', ignoreRoot=True ):
511 """
512 Run `du -h` on the folder path and verifies the folder(s) size is
513 less than the given size. Note that if multiple subdirectories are
514 present, the result will be the OR of all the individual subdirectories.
515
516 Arguments:
517 path - A string containing the path supplied to the du command
518 size - The number portion of the file size that the results will be compared to
519 unit - The unit portion of the file size that the results will be compared to
520 ignoreRoot - If True, will ignore the "root" of the path supplied to du. I.E. will ignore `.`
521
522 Returns True if the folder(s) size(s) are less than SIZE UNITS, else returns False
523 """
524 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
525 unitsList = [ 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]
526 try:
527 # make sure we convert units if size is too big
528 size = float( size )
529 if size >= 1000:
530 size = size / 1000
531 unit = unitsList[ unitsList.index( unit + 1 ) ]
532 cmdStr = "du -h " + path
533 self.handle.sendline( cmdStr )
534 self.handle.expect( self.prompt )
535 output = self.handle.before
536 assert "cannot access" not in output
537 assert "command not found" not in output
538 main.log.debug( output )
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700539 lines = [ line for line in output.split( '\r\n' ) ]
Jon Hall4173b242017-09-12 17:04:38 -0700540 retValue = True
541 if ignoreRoot:
542 lastIndex = -2
543 else:
544 lastIndex = -1
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700545 for line in lines[ 1:lastIndex ]:
Jon Hall4173b242017-09-12 17:04:38 -0700546 parsed = line.split()
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700547 sizeMatch = parsed[ 0 ]
548 folder = parsed[ 1 ]
Jon Hall4173b242017-09-12 17:04:38 -0700549 match = re.search( sizeRe, sizeMatch )
550 num = match.group( 'number' )
551 unitMatch = match.group( 'unit' )
552 if unitsList.index( unitMatch ) < unitsList.index( unit ):
553 retValue &= True
554 elif unitsList.index( unitMatch ) == unitsList.index( unit ):
555 if float( num ) < float( size ):
556 retValue &= True
557 else:
558 retValue &= False
559 elif unitsList.index( unitMatch ) > unitsList.index( unit ):
560 retValue &= False
561 return retValue
562 except AssertionError:
563 main.log.error( self.name + ": Could not execute command: " + output )
564 return False
Jon Hall43060f62020-06-23 13:13:33 -0700565 except ValueError as e:
566 main.log.error( self.name + ": Error parsing output: " + output )
567 main.log.error( e )
568 return False
Jon Hall4173b242017-09-12 17:04:38 -0700569 except pexpect.TIMEOUT:
570 main.log.exception( self.name + ": TIMEOUT exception found" )
571 main.log.error( self.name + ": " + self.handle.before )
572 return False
573 except pexpect.EOF:
574 main.log.error( self.name + ": EOF exception found" )
575 main.log.error( self.name + ": " + self.handle.before )
576 main.cleanAndExit()
Jon Hall0e240372018-05-02 11:21:57 -0700577
Jon Hall22a3bcf2021-07-23 11:40:11 -0700578 def fileSize( self, path, inBytes=True ):
579 """
580 Run `du` on the file path and returns the file size
581
582 Arguments:
583 path - A string containing the path supplied to the du command
584 Optional Arguments:
585 inBytes - Display size in bytes, defaults to true
586
587 Returns the size of the file as an int
588 """
589 sizeRe = r'(?P<number>\d+\.*\d*)(?P<unit>\D)'
590 try:
591 cmdStr = "du %s %s" % ( "-b" if inBytes else "", path )
592 self.handle.sendline( cmdStr )
593 self.handle.expect( self.prompt )
594 output = self.handle.before
595 assert "cannot access" not in output
596 assert "command not found" not in output
597 assert "No such file or directory" not in output
598 main.log.debug( output )
599 lines = [ line for line in output.split( '\r\n' ) ]
600 return int( lines[1].split()[0] )
601 except AssertionError:
602 main.log.error( self.name + ": Could not execute command: " + output )
603 return False
604 except ValueError as e:
605 main.log.error( self.name + ": Error parsing output: " + output )
606 main.log.error( e )
607 return False
608 except pexpect.TIMEOUT:
609 main.log.exception( self.name + ": TIMEOUT exception found" )
610 main.log.error( self.name + ": " + self.handle.before )
611 return False
612 except pexpect.EOF:
613 main.log.error( self.name + ": EOF exception found" )
614 main.log.error( self.name + ": " + self.handle.before )
615 main.cleanAndExit()
616
Jon Hall0e240372018-05-02 11:21:57 -0700617 def setEnv( self, variable, value=None ):
618 """
619 Sets the environment variable to the given value for the current shell session.
620 If value is None, will unset the variable.
621
622 Required Arguments:
623 variable - The name of the environment variable to set.
624
625 Optional Arguments:
626 value - The value to set the variable to. ( Defaults to None, which unsets the variable )
627
628 Returns True if no errors are detected else returns False
629 """
630 try:
631 if value:
632 cmd = "export {}={}".format( variable, value )
633 else:
634 cmd = "unset {}".format( variable )
635 self.handle.sendline( cmd )
636 self.handle.expect( self.prompt )
Jon Hall3c0114c2020-08-11 15:07:42 -0700637 output = self.handle.before
638 main.log.debug( output )
Jon Hall0e240372018-05-02 11:21:57 -0700639 return True
640 except AssertionError:
641 main.log.error( self.name + ": Could not execute command: " + output )
642 return False
643 except pexpect.TIMEOUT:
644 main.log.exception( self.name + ": TIMEOUT exception found" )
645 main.log.error( self.name + ": " + self.handle.before )
646 return False
647 except pexpect.EOF:
648 main.log.error( self.name + ": EOF exception found" )
649 main.log.error( self.name + ": " + self.handle.before )
650 main.cleanAndExit()
You Wangb65d2372018-08-17 15:37:59 -0700651
652 def exitFromCmd( self, expect, retry=10 ):
653 """
654 Call this function when sending ctrl+c is required to kill the current
655 command. It will retry multiple times until the running command is
656 completely killed and expected string is returned from the handle.
657 Required:
You Wangd4fae5c2018-08-22 13:56:49 -0700658 expect: expected string or list of strings which indicates that the
659 previous command was killed successfully.
You Wangb65d2372018-08-17 15:37:59 -0700660 Optional:
661 retry: maximum number of ctrl+c that will be sent.
662 """
You Wangd4fae5c2018-08-22 13:56:49 -0700663 expect = [ expect ] if isinstance( expect, str ) else expect
You Wangb65d2372018-08-17 15:37:59 -0700664 try:
665 while retry >= 0:
666 main.log.debug( self.name + ": sending ctrl+c to kill the command" )
667 self.handle.send( "\x03" )
You Wangd4fae5c2018-08-22 13:56:49 -0700668 i = self.handle.expect( expect + [ pexpect.TIMEOUT ], timeout=3 )
You Wangb65d2372018-08-17 15:37:59 -0700669 main.log.debug( self.handle.before )
You Wangd4fae5c2018-08-22 13:56:49 -0700670 if i < len( expect ):
You Wangb65d2372018-08-17 15:37:59 -0700671 main.log.debug( self.name + ": successfully killed the command" )
672 return main.TRUE
673 retry -= 1
674 main.log.warn( self.name + ": failed to kill the command" )
675 return main.FALSE
676 except pexpect.EOF:
677 main.log.error( self.name + ": EOF exception found" )
678 main.log.error( self.name + ": " + self.handle.before )
679 return main.FALSE
680 except Exception:
681 main.log.exception( self.name + ": Uncaught exception!" )
682 return main.FALSE
Jon Hall43060f62020-06-23 13:13:33 -0700683
684 def cleanOutput( self, output, debug=False ):
685 """
686 Clean ANSI characters from output
687 """
688 ansiEscape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
689 cleaned = ansiEscape.sub( '', output )
690 if debug:
691 main.log.debug( self.name + ": cleanOutput:" )
692 main.log.debug( self.name + ": " + repr( cleaned ) )
693 return cleaned
Jon Hall3c0114c2020-08-11 15:07:42 -0700694
695 def dockerPull( self, image, tag=None ):
696 """
697 Pull a docker image from a registry
698 """
699 try:
700 imgStr = "%s%s" % ( image, ":%s" % tag if tag else "" )
701 cmdStr = "docker pull %s" % imgStr
702 main.log.info( self.name + ": sending: " + cmdStr )
703 self.handle.sendline( cmdStr)
704 i = self.handle.expect( [ self.prompt,
705 "Error response from daemon",
706 pexpect.TIMEOUT ], 120 )
707 if i == 0:
708 return main.TRUE
709 else:
710 main.log.error( self.name + ": Error pulling docker image " + imgStr )
711 output = self.handle.before + str( self.handle.after )
712 if i == 1:
713 self.handle.expect( self.prompt )
714 output += self.handle.before + str( self.handle.after )
715 main.log.debug( self.name + ": " + output )
716 return main.FALSE
717 except pexpect.EOF:
718 main.log.error( self.name + ": EOF exception found" )
719 main.log.error( self.name + ": " + self.handle.before )
720 return main.FALSE
721 except Exception:
722 main.log.exception( self.name + ": Uncaught exception!" )
723 return main.FALSE
724
725 def dockerBuild( self, path, imageTag, pull=False, options="", timeout=600 ):
726 """
727 Build a docker image
728 Required Arguments:
729 - path: Path to the dockerfile, it is recommended to avoid relative paths
730 - imageTag: Give a tag to the built docker image
731 Optional Arguments:
732 - pull: Whether to attempt to pull latest images before building
733 - options: A string containing any addition optional arguments
734 for the docker build command
735 - timeout: How many seconds to wait for the build to complete
736 """
737 try:
738 response = main.TRUE
739 if pull:
740 options = "--pull " + options
741 cmdStr = "docker build -t %s %s %s" % ( imageTag, options, path )
742 main.log.info( self.name + ": sending: " + cmdStr )
743 self.handle.sendline( cmdStr)
744 i = self.handle.expect( [ "Successfully built",
745 "Error response from daemon",
746 pexpect.TIMEOUT ], timeout=timeout )
747 output = self.handle.before
748 if i == 0:
749 output += self.handle.after
750 self.handle.expect( self.prompt )
751 output += self.handle.before + self.handle.after
752 return response
753 elif i == 1:
754 response = main.FALSE
755 output += self.handle.after
756 self.handle.expect( self.prompt )
757 output += self.handle.before + self.handle.after
758 elif i == 2:
759 response = main.FALSE
760 main.log.error( self.name + ": Error building docker image" )
761 main.log.debug( self.name + ": " + output )
762 return response
763 except pexpect.EOF:
764 main.log.error( self.name + ": EOF exception found" )
765 main.log.error( self.name + ": " + self.handle.before )
766 return main.FALSE
767 except Exception:
768 main.log.exception( self.name + ": Uncaught exception!" )
769 return main.FALSE
770
771 def dockerStop( self, containerName ):
772 """
773 Stop a docker container
774 Required Arguments:
775 - containerName: Name of the container to stop
776 """
777 try:
778 cmdStr = "docker stop %s" % ( containerName )
779 main.log.info( self.name + ": sending: " + cmdStr )
780 self.handle.sendline( cmdStr)
781 i = self.handle.expect( [ self.prompt,
782 "Error response from daemon",
783 pexpect.TIMEOUT ], 120 )
784 output = self.handle.before
785 if i == 0:
786 return main.TRUE
787 elif i == 1:
788 output += self.handle.after
789 self.handle.expect( self.prompt )
790 output += self.handle.before
791 elif i == 2:
792 pass
793 main.log.debug( "%s: %s" % ( self.name, output ) )
794 if "No such container" in output:
795 return main.TRUE
796 main.log.error( self.name + ": Error stopping docker image" )
797 main.log.debug( self.name + ": " + output )
798 return main.FALSE
799 except pexpect.EOF:
800 main.log.error( self.name + ": EOF exception found" )
801 main.log.error( self.name + ": " + self.handle.before )
802 return main.FALSE
803 except Exception:
804 main.log.exception( self.name + ": Uncaught exception!" )
805 return main.FALSE
806
Jon Hall06fd0df2021-01-25 15:50:06 -0800807 def dockerRun( self, image, containerName, options="", imageArgs="", background=False ):
Jon Hall3c0114c2020-08-11 15:07:42 -0700808 """
809 Run a docker image
810 Required Arguments:
811 - containerName: Give a name to the container once its started
812 - image: Run the given image
813 Optional Arguments:
814 - options: A string containing any addition optional arguments
815 for the docker run command
816 - imageArgs: A string containing command line arguments for the
817 command run by docker
818 """
819 try:
820 cmdStr = "docker run --name %s %s %s %s" % ( containerName,
821 options if options else "",
822 image,
823 imageArgs )
Jon Hall06fd0df2021-01-25 15:50:06 -0800824 if background:
825 cmdStr += " &"
Jon Hall3c0114c2020-08-11 15:07:42 -0700826 main.log.info( self.name + ": sending: " + cmdStr )
827 self.handle.sendline( cmdStr)
828 i = self.handle.expect( [ self.prompt,
829 "Error response from daemon",
830 pexpect.TIMEOUT ], 120 )
831 if i == 0:
832 return main.TRUE
833 else:
834 output = self.handle.before
835 main.log.debug( self.name + ": " + output )
836 main.log.error( self.name + ": Error running docker image" )
837 if i == 1:
838 output += self.handle.after
839 self.handle.expect( self.prompt )
840 output += self.handle.before + self.handle.after
841 main.log.debug( self.name + ": " + output )
842 return main.FALSE
843 except pexpect.EOF:
844 main.log.error( self.name + ": EOF exception found" )
845 main.log.error( self.name + ": " + self.handle.before )
846 return main.FALSE
847 except Exception:
848 main.log.exception( self.name + ": Uncaught exception!" )
849 return main.FALSE
850
851 def dockerAttach( self, containerName, dockerPrompt="" ):
852 """
853 Attach to a docker image
854 Required Arguments:
855 - containerName: The name of the container to attach to
856 Optional Arguments:
857 - dockerPrompt: a regex for matching the docker shell prompt
858 """
859 try:
860 if dockerPrompt:
861 self.dockerPrompt = dockerPrompt
862 cmdStr = "docker attach %s" % containerName
863 main.log.info( self.name + ": sending: " + cmdStr )
864 self.handle.sendline( cmdStr)
865 i = self.handle.expect( [ self.dockerPrompt,
866 "Error response from daemon",
867 pexpect.TIMEOUT ] )
868 if i == 0:
869 self.inDocker = True
870 return main.TRUE
871 else:
872 main.log.error( self.name + ": Error connecting to docker container" )
873 output = self.handle.before + str( self.handle.after )
874 if i == 1:
875 self.handle.expect( self.prompt )
876 output += self.handle.before + str( self.handle.after )
877 main.log.debug( self.name + ": " + output )
878 return main.FALSE
879 except pexpect.EOF:
880 main.log.error( self.name + ": EOF exception found" )
881 main.log.error( self.name + ": " + self.handle.before )
882 return main.FALSE
883 except AttributeError as e:
884 main.log.exception( self.name + ": AttributeError - " + str( e ) )
885 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
886 main.cleanup()
887 main.exit()
888 except Exception:
889 main.log.exception( self.name + ": Uncaught exception!" )
890 return main.FALSE
891
892 def dockerExec( self, containerName, command="/bin/bash", options="-it", dockerPrompt="" ):
893 """
894 Attach to a docker image
895 Required Arguments:
896 - containerName: The name of the container to attach to
897 Optional Arguments:
898 - command: Command to run in the docker container
899 - options: Docker exec options
900 - dockerPrompt: a regex for matching the docker shell prompt
901 """
902 try:
903 if dockerPrompt:
904 self.dockerPrompt = dockerPrompt
905 cmdStr = "docker exec %s %s %s" % ( options, containerName, command )
906 main.log.info( self.name + ": sending: " + cmdStr )
907 self.handle.sendline( cmdStr)
908 i = self.handle.expect( [ self.dockerPrompt,
909 "Error response from daemon",
910 pexpect.TIMEOUT ] )
911 if i == 0:
912 self.inDocker = True
913 return main.TRUE
914 else:
915 main.log.error( self.name + ": Error connecting to docker container" )
916 output = self.handle.before + str( self.handle.after )
917 if i == 1:
918 self.handle.expect( self.prompt )
919 output += self.handle.before + str( self.handle.after )
920 main.log.debug( self.name + ": " + output )
921 return main.FALSE
922 except pexpect.EOF:
923 main.log.error( self.name + ": EOF exception found" )
924 main.log.error( self.name + ": " + self.handle.before )
925 return main.FALSE
926 except AttributeError as e:
927 main.log.exception( self.name + ": AttributeError - " + str( e ) )
928 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
929 main.cleanup()
930 main.exit()
931 except Exception:
932 main.log.exception( self.name + ": Uncaught exception!" )
933 return main.FALSE
934
935 def dockerCp( self, containerName, dockerPath, hostPath, direction="from" ):
936 """
937 Copy a file from/to a docker container to the host
938 Required Arguments:
939 - containerName: The name of the container to copy from/to
940 - dockerPath: the path in the container to copy from/to
941 - hostPath: the path on the host to copy to/from
942 Optional Arguments:
943 - direction: Choose whether to copy "from" the container or "to" the container
944 """
945 try:
946 cmdStr = "docker cp "
947 if direction == "from":
948 cmdStr += "%s:%s %s" % ( containerName, dockerPath, hostPath )
949 elif direction == "to":
950 cmdStr += "%s %s:%s" % ( hostPath, containerName, dockerPath )
951 main.log.info( self.name + ": sending: " + cmdStr )
952 self.handle.sendline( cmdStr)
953 i = self.handle.expect( [ self.prompt,
954 "Error",
955 pexpect.TIMEOUT ] )
956 if i == 0:
957 retValue = main.TRUE
958 else:
959 main.log.error( self.name + ": Error in docker cp" )
960 output = self.handle.before + str( self.handle.after )
961 if i == 1:
962 self.handle.expect( self.prompt )
963 output += self.handle.before + str( self.handle.after )
964 main.log.debug( self.name + ": " + output )
965 retValue = main.FALSE
966 return retValue
967 except pexpect.EOF:
968 main.log.error( self.name + ": EOF exception found" )
969 main.log.error( self.name + ": " + self.handle.before )
970 return main.FALSE
971 except AttributeError as e:
972 main.log.exception( self.name + ": AttributeError - " + str( e ) )
973 main.log.warn( self.name + ": Make sure dockerPrompt is set" )
974 main.cleanup()
975 main.exit()
976 except Exception:
977 main.log.exception( self.name + ": Uncaught exception!" )
978 return main.FALSE
979
980 def dockerDisconnect( self ):
981 """
982 Send ctrl-c, ctrl-d to session, which should close and exit the
983 attached docker session. This will likely exit the running program
984 in the container and also stop the container.
985 """
986 try:
987 cmdStr = "\x03"
988 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
989 self.handle.send( cmdStr)
990 cmdStr = "\x04"
991 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
992 self.handle.send( cmdStr)
993 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
994 if i == 0:
995 self.inDocker = False
996 return main.TRUE
997 else:
998 main.log.error( self.name + ": Error disconnecting from docker image" )
999 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1000 return main.FALSE
1001 except pexpect.EOF:
1002 main.log.error( self.name + ": EOF exception found" )
1003 main.log.error( self.name + ": " + self.handle.before )
1004 return main.FALSE
1005 except Exception:
1006 main.log.exception( self.name + ": Uncaught exception!" )
1007 return main.FALSE
Jon Hall06fd0df2021-01-25 15:50:06 -08001008
1009# TODO: How is this different from exitFromCmd used elsewhere?
1010 def exitFromProcess( self ):
1011 """
1012 Send ctrl-c, which should close and exit the program
1013 """
1014 try:
1015 cmdStr = "\x03"
1016 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1017 self.handle.send( cmdStr)
1018 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
1019 if i == 0:
1020 return main.TRUE
1021 else:
1022 main.log.error( self.name + ": Error exiting process" )
1023 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1024 return main.FALSE
1025 except pexpect.EOF:
1026 main.log.error( self.name + ": EOF exception found" )
1027 main.log.error( self.name + ": " + self.handle.before )
1028 return main.FALSE
1029 except Exception:
1030 main.log.exception( self.name + ": Uncaught exception!" )
1031 return main.FALSE
1032
1033 def preDisconnect( self ):
1034 """
1035 A Stub for a function that will be called before disconnect.
1036 This can be set if for instance, the shell is running a program
1037 and needs to exit the program before disconnecting from the component
1038 """
1039 print "preDisconnect"
1040 return main.TRUE
1041
Jon Hall22a3bcf2021-07-23 11:40:11 -07001042 def kubectlGetPodNames( self, kubeconfig=None, namespace=None, app=None, name=None,
1043 nodeName=None, status=None ):
Jon Hall06fd0df2021-01-25 15:50:06 -08001044 """
1045 Use kubectl to get the names of pods
1046 Optional Arguments:
1047 - kubeconfig: The path to a kubeconfig file
1048 - namespace: The namespace to search in
1049 - app: Get pods belonging to a specific app
1050 - name: Get pods with a specific name label
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001051 - nodeName: Get pods on a specific node
Jon Hall22a3bcf2021-07-23 11:40:11 -07001052 - status: Get pods with the specified Status
Jon Hall06fd0df2021-01-25 15:50:06 -08001053 Returns a list containing the names of the pods or
1054 main.FALSE on Error
1055 """
1056
1057 try:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001058 self.handle.sendline( "" )
1059 self.handle.expect( self.prompt )
1060 main.log.debug( self.handle.before + self.handle.after )
1061 cmdStr = "kubectl %s %s get pods %s %s %s %s --output=jsonpath='{.items..metadata.name}{\"\\n\"}'" % (
Jon Hall06fd0df2021-01-25 15:50:06 -08001062 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1063 "-n %s" % namespace if namespace else "",
1064 "-l app=%s" % app if app else "",
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001065 "-l name=%s" % name if name else "",
Jon Hall22a3bcf2021-07-23 11:40:11 -07001066 "--field-selector=spec.nodeName=%s" % nodeName if nodeName else "",
1067 "--field-selector=status.phase=%s" % status if status else "" )
Jon Hall06fd0df2021-01-25 15:50:06 -08001068 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1069 self.handle.sendline( cmdStr )
Jon Hall2941fce2021-04-13 10:38:23 -07001070 i = self.handle.expect( [ "not found", "error", "The connection to the server", "Unable to find", "No resources found", self.prompt ] )
1071 if i == 4:
1072 # Command worked, but returned no pods
1073 output = self.handle.before + self.handle.after
1074 main.log.warn( self.name + ": " + output )
1075 return []
1076 elif i == 5:
1077 # Command returned pods
Jon Hall06fd0df2021-01-25 15:50:06 -08001078 output = self.handle.before + self.handle.after
1079 names = output.split( '\r\n' )[1].split()
1080 return names
1081 else:
Jon Hall2941fce2021-04-13 10:38:23 -07001082 # Some error occured
Jon Hall06fd0df2021-01-25 15:50:06 -08001083 main.log.error( self.name + ": Error executing command" )
1084 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1085 return main.FALSE
1086 except pexpect.EOF:
1087 main.log.error( self.name + ": EOF exception found" )
1088 main.log.error( self.name + ": " + self.handle.before )
1089 return main.FALSE
1090 except pexpect.TIMEOUT:
1091 main.log.exception( self.name + ": TIMEOUT exception found" )
1092 main.log.error( self.name + ": " + self.handle.before )
1093 return main.FALSE
1094 except Exception:
1095 main.log.exception( self.name + ": Uncaught exception!" )
1096 return main.FALSE
1097
1098 def kubectlDescribe( self, describeString, dstPath, kubeconfig=None, namespace=None ):
1099 """
1100 Use kubectl to get the logs from a pod
1101 Required Arguments:
1102 - describeString: The string passed to the cli. Example: "pods"
1103 - dstPath: The location to save the logs to
1104 Optional Arguments:
1105 - kubeconfig: The path to a kubeconfig file
1106 - namespace: The namespace to search in
1107 Returns main.TRUE or
1108 main.FALSE on Error
1109 """
1110
1111 try:
1112 cmdStr = "kubectl %s %s describe %s > %s " % (
1113 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1114 "-n %s" % namespace if namespace else "",
1115 describeString,
1116 dstPath )
1117 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1118 self.handle.sendline( cmdStr )
1119 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1120 if i == 3:
1121 main.log.debug( self.name + ": " + self.handle.before )
1122 return main.TRUE
1123 else:
1124 main.log.error( self.name + ": Error executing command" )
1125 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1126 return main.FALSE
1127 except pexpect.EOF:
1128 main.log.error( self.name + ": EOF exception found" )
1129 main.log.error( self.name + ": " + self.handle.before )
1130 return main.FALSE
1131 except pexpect.TIMEOUT:
1132 main.log.exception( self.name + ": TIMEOUT exception found" )
1133 main.log.error( self.name + ": " + self.handle.before )
1134 return main.FALSE
1135 except Exception:
1136 main.log.exception( self.name + ": Uncaught exception!" )
1137 return main.FALSE
1138
1139 def kubectlPodNodes( self, dstPath=None, kubeconfig=None, namespace=None ):
1140 """
Jon Halla16b4db2021-10-20 14:11:59 -07001141 Use kubectl to get the pod to node mappings
Jon Hall06fd0df2021-01-25 15:50:06 -08001142 Optional Arguments:
1143 - dstPath: The location to save the logs to
1144 - kubeconfig: The path to a kubeconfig file
1145 - namespace: The namespace to search in
1146 Returns main.TRUE if dstPath is given, else the output of the command or
1147 main.FALSE on Error
1148 """
Jon Hall06fd0df2021-01-25 15:50:06 -08001149 try:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001150 self.handle.sendline( "" )
1151 self.handle.expect( self.prompt )
1152 main.log.debug( self.handle.before + self.handle.after )
Jon Hall50a00012021-03-08 11:06:11 -08001153 cmdStr = "kubectl %s %s get pods -o wide %s " % (
Jon Hall06fd0df2021-01-25 15:50:06 -08001154 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1155 "-n %s" % namespace if namespace else "",
1156 " > %s" % dstPath if dstPath else "" )
1157 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1158 self.handle.sendline( cmdStr )
1159 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1160 if i == 3:
1161 output = self.handle.before
1162 main.log.debug( self.name + ": " + output )
1163 return output if dstPath else main.TRUE
1164 else:
1165 main.log.error( self.name + ": Error executing command" )
1166 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1167 return main.FALSE
1168 except pexpect.EOF:
1169 main.log.error( self.name + ": EOF exception found" )
1170 main.log.error( self.name + ": " + self.handle.before )
1171 return main.FALSE
1172 except pexpect.TIMEOUT:
1173 main.log.exception( self.name + ": TIMEOUT exception found" )
1174 main.log.error( self.name + ": " + self.handle.before )
1175 return main.FALSE
1176 except Exception:
1177 main.log.exception( self.name + ": Uncaught exception!" )
1178 return main.FALSE
1179
Jon Halla16b4db2021-10-20 14:11:59 -07001180 def kubectlGetPodNode( self, podName, kubeconfig=None, namespace=None ):
1181 """
1182 Use kubectl to get the node a given pod is running on
1183 Arguments:
1184 - podName: The name of the pod
1185 Optional Arguments:
1186 - kubeconfig: The path to a kubeconfig file
1187 - namespace: The namespace to search in
1188 Returns a string of the node name or None
1189 """
1190 try:
1191 self.handle.sendline( "" )
1192 self.handle.expect( self.prompt )
1193 main.log.debug( self.handle.before + self.handle.after )
1194 cmdStr = "kubectl %s %s get pods %s --output=jsonpath='{.spec.nodeName}{\"\\n\"}'" % (
1195 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1196 "-n %s" % namespace if namespace else "",
1197 podName )
1198 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1199 self.handle.sendline( cmdStr )
1200 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1201 if i == 3:
1202 output = self.handle.before
1203 main.log.debug( self.name + ": " + output )
1204 output = output.splitlines()
1205 main.log.warn( output )
1206 return output[1] if len( output ) == 3 else None
1207 else:
1208 main.log.error( self.name + ": Error executing command" )
1209 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1210 return None
1211 except pexpect.EOF:
1212 main.log.error( self.name + ": EOF exception found" )
1213 main.log.error( self.name + ": " + self.handle.before )
1214 return None
1215 except pexpect.TIMEOUT:
1216 main.log.exception( self.name + ": TIMEOUT exception found" )
1217 main.log.error( self.name + ": " + self.handle.before )
1218 return None
1219 except Exception:
1220 main.log.exception( self.name + ": Uncaught exception!" )
1221 return None
1222
Jon Halla4a79312022-01-25 17:16:53 -08001223 def kubectlCmd( self, cmd, kubeconfig=None, namespace=None ):
1224 """
1225 Run an arbitrary command using kubectl
1226 Arguments:
1227 - cmd: Command string to send to kubectl
1228 Optional Arguments:
1229 - kubeconfig: The path to a kubeconfig file
1230 - namespace: The namespace to search in
1231 Returns a string of the node name or None
1232 """
1233 try:
1234 self.handle.sendline( "" )
1235 self.handle.expect( self.prompt )
1236 main.log.debug( self.handle.before + self.handle.after )
1237 cmdStr = "kubectl %s %s %s" % (
1238 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1239 "-n %s" % namespace if namespace else "",
1240 cmd )
1241 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1242 self.handle.sendline( cmdStr )
1243 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
1244 if i == 3:
1245 output = self.handle.before
1246 main.log.debug( self.name + ": " + output )
1247 output = output.splitlines()
1248 main.log.warn( output )
1249 return output[1] if len( output ) == 3 else None
1250 else:
1251 main.log.error( self.name + ": Error executing command" )
1252 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1253 return None
1254 except pexpect.EOF:
1255 main.log.error( self.name + ": EOF exception found" )
1256 main.log.error( self.name + ": " + self.handle.before )
1257 return None
1258 except pexpect.TIMEOUT:
1259 main.log.exception( self.name + ": TIMEOUT exception found" )
1260 main.log.error( self.name + ": " + self.handle.before )
1261 return None
1262 except Exception:
1263 main.log.exception( self.name + ": Uncaught exception!" )
1264 return None
1265
Jon Halla7b27e62021-06-29 12:13:51 -07001266 def sternLogs( self, podString, dstPath, kubeconfig=None, namespace=None, since='1h', wait=60 ):
1267 """
1268 Use stern to get the logs from a pod
1269 Required Arguments:
1270 - podString: The name of the pod or partial name of the pods to get the logs of
1271 - dstPath: The location to save the logs to
1272 Optional Arguments:
1273 - kubeconfig: The path to a kubeconfig file
1274 - namespace: The namespace to search in
1275 - since: Return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to 1h
1276 - wait: How long to wait, in seconds, before killing the process. Stern does not currently
Jon Halldd05bbc2022-01-27 12:14:50 -08001277 support a way to exit if caught up to present time. If negative, leaves the stern
1278 process running, tailing the logs and returns main.TRUE. Defaults to 60 seconds
Jon Halla7b27e62021-06-29 12:13:51 -07001279 Returns main.TRUE or
1280 main.FALSE on Error
1281 """
1282 import time
1283 try:
1284 cmdStr = "stern %s %s %s %s > %s " % (
1285 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1286 "-n %s" % namespace if namespace else "",
1287 "--since %s" % since if since else "",
1288 podString,
1289 dstPath )
1290 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1291 self.handle.sendline( cmdStr )
Jon Hall22a3bcf2021-07-23 11:40:11 -07001292 if int( wait ) >= 0:
Jon Halldd05bbc2022-01-27 12:14:50 -08001293 i = self.handle.expect( [ "not found",
1294 "Error: ",
1295 "The connection to the server",
1296 self.prompt,
1297 pexpect.TIMEOUT ], timeout=int( wait ) )
Jon Hall22a3bcf2021-07-23 11:40:11 -07001298 if i == 3:
1299 main.log.debug( self.name + ": " + self.handle.before )
1300 return main.TRUE
Jon Halldd05bbc2022-01-27 12:14:50 -08001301 if i == 4:
1302 self.handle.send( '\x03' ) # CTRL-C
1303 self.handle.expect( self.prompt, timeout=5 )
1304 main.log.debug( self.name + ": " + self.handle.before )
1305 return main.TRUE
Jon Hall22a3bcf2021-07-23 11:40:11 -07001306 else:
1307 main.log.error( self.name + ": Error executing command" )
1308 response = self.handle.before + str( self.handle.after )
1309 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ], timeout=5 )
1310 response += self.handle.before + str( self.handle.after )
1311 main.log.debug( self.name + ": " + response )
1312 return main.FALSE
Jon Halla7b27e62021-06-29 12:13:51 -07001313 else:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001314 self.preDisconnect = self.exitFromProcess
1315 return main.TRUE
Jon Halla7b27e62021-06-29 12:13:51 -07001316 except pexpect.EOF:
1317 main.log.error( self.name + ": EOF exception found" )
1318 main.log.error( self.name + ": " + self.handle.before )
1319 return main.FALSE
1320 except pexpect.TIMEOUT:
1321 main.log.exception( self.name + ": TIMEOUT exception found" )
1322 main.log.error( self.name + ": " + self.handle.before )
1323 return main.FALSE
1324 except Exception:
1325 main.log.exception( self.name + ": Uncaught exception!" )
1326 return main.FALSE
1327
Jon Hall06fd0df2021-01-25 15:50:06 -08001328 def kubectlLogs( self, podName, dstPath, kubeconfig=None, namespace=None, timeout=240 ):
1329 """
1330 Use kubectl to get the logs from a pod
1331 Required Arguments:
1332 - podName: The name of the pod to get the logs of
1333 - dstPath: The location to save the logs to
1334 Optional Arguments:
1335 - kubeconfig: The path to a kubeconfig file
1336 - namespace: The namespace to search in
1337 - timeout: Timeout for command to return. The longer the logs, the longer it will take to fetch them.
1338 Returns main.TRUE or
1339 main.FALSE on Error
1340 """
1341
1342 try:
1343 cmdStr = "kubectl %s %s logs %s > %s " % (
1344 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1345 "-n %s" % namespace if namespace else "",
1346 podName,
1347 dstPath )
1348 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1349 self.handle.sendline( cmdStr )
1350 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ], timeout=timeout )
1351 if i == 3:
1352 main.log.debug( self.name + ": " + self.handle.before )
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
1369
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001370 def kubectlCp( self, podName, srcPath, dstPath, kubeconfig=None, namespace=None, timeout=240 ):
1371 """
1372 Use kubectl to get a file from a pod
1373 Required Arguments:
1374 - podName: The name of the pod to get the logs of
1375 - srcPath: The file to copy from the pod
1376 - dstPath: The location to save the file to locally
1377 Optional Arguments:
1378 - kubeconfig: The path to a kubeconfig file
1379 - namespace: The namespace to search in
1380 - timeout: Timeout for command to return. The longer the logs, the longer it will take to fetch them.
1381 Returns main.TRUE or
1382 main.FALSE on Error
1383 """
1384
1385 try:
1386 cmdStr = "kubectl %s %s cp %s:%s %s" % (
1387 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1388 "-n %s" % namespace if namespace else "",
1389 podName,
1390 srcPath,
1391 dstPath )
1392 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1393 self.handle.sendline( cmdStr )
1394 i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ], timeout=timeout )
1395 if i == 3:
1396 main.log.debug( self.name + ": " + self.handle.before )
1397 return main.TRUE
1398 else:
Jon Hall22a3bcf2021-07-23 11:40:11 -07001399 output = self.handle.before + str( self.handle.after )
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001400 main.log.error( self.name + ": Error executing command" )
Jon Hall22a3bcf2021-07-23 11:40:11 -07001401 self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
1402 output += self.handle.before + str( self.handle.after )
1403 main.log.debug( self.name + ": " + output )
Jon Hallbe3a2ac2021-03-15 12:28:06 -07001404 return main.FALSE
1405 except pexpect.EOF:
1406 main.log.error( self.name + ": EOF exception found" )
1407 main.log.error( self.name + ": " + self.handle.before )
1408 return main.FALSE
1409 except pexpect.TIMEOUT:
1410 main.log.exception( self.name + ": TIMEOUT exception found" )
1411 main.log.error( self.name + ": " + self.handle.before )
1412 return main.FALSE
1413 except Exception:
1414 main.log.exception( self.name + ": Uncaught exception!" )
1415 return main.FALSE
1416
Jon Halla16b4db2021-10-20 14:11:59 -07001417 def kubectlPortForward( self, podName, portsList, kubeconfig=None, namespace=None ):
Jon Hall06fd0df2021-01-25 15:50:06 -08001418 """
1419 Use kubectl to setup port forwarding from the local machine to the kubernetes pod
1420
Jon Halla16b4db2021-10-20 14:11:59 -07001421 Note: This cli command does not return until the port forwarding session is ended.
Jon Hall06fd0df2021-01-25 15:50:06 -08001422
1423 Required Arguments:
1424 - podName: The name of the pod as a string
1425 - portsList: The list of ports to forward, as a string. see kubectl help for details
1426 Optional Arguments:
1427 - kubeconfig: The path to a kubeconfig file
1428 - namespace: The namespace to search in
Jon Halla16b4db2021-10-20 14:11:59 -07001429 Returns main.TRUE if a port-forward session was created or main.FALSE on Error
Jon Hall06fd0df2021-01-25 15:50:06 -08001430
1431
1432 """
1433 try:
1434 cmdStr = "kubectl %s %s port-forward pod/%s %s" % (
1435 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1436 "-n %s" % namespace if namespace else "",
1437 podName,
1438 portsList )
Jon Halldecf0982022-02-03 13:57:11 -08001439
1440 self.clearBuffer()
Jon Hall06fd0df2021-01-25 15:50:06 -08001441 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1442 self.handle.sendline( cmdStr )
Jon Halla16b4db2021-10-20 14:11:59 -07001443 self.handle.expect( "pod/%s" % podName )
1444 output = self.handle.before + self.handle.after
Jon Hall06fd0df2021-01-25 15:50:06 -08001445 i = self.handle.expect( [ "not found", "error", "closed/timedout",
Jon Halldecf0982022-02-03 13:57:11 -08001446 self.prompt, "The connection to the server",
1447 "Forwarding from", pexpect.TIMEOUT ] )
Jon Halla16b4db2021-10-20 14:11:59 -07001448 output += self.handle.before + str( self.handle.after )
Jon Hall06fd0df2021-01-25 15:50:06 -08001449 # NOTE: This won't clear the buffer entirely, and each time the port forward
1450 # is used, another line will be added to the buffer. We need to make
1451 # sure we clear the buffer before using this component again.
1452
1453 if i == 5:
1454 # Setup preDisconnect function
1455 self.preDisconnect = self.exitFromProcess
Jon Halla16b4db2021-10-20 14:11:59 -07001456 self.portForwardList = portsList
Jon Hall06fd0df2021-01-25 15:50:06 -08001457 return main.TRUE
Jon Halldecf0982022-02-03 13:57:11 -08001458 elif i == 6:
1459 return self.checkPortForward( podName, portsList, kubeconfig, namespace )
Jon Hall06fd0df2021-01-25 15:50:06 -08001460 else:
1461 main.log.error( self.name + ": Error executing command" )
Jon Halla16b4db2021-10-20 14:11:59 -07001462 main.log.debug( self.name + ": " + output )
Jon Hall06fd0df2021-01-25 15:50:06 -08001463 return main.FALSE
1464 except pexpect.EOF:
1465 main.log.error( self.name + ": EOF exception found" )
1466 main.log.error( self.name + ": " + self.handle.before )
1467 return main.FALSE
1468 except pexpect.TIMEOUT:
1469 main.log.exception( self.name + ": TIMEOUT exception found" )
1470 main.log.error( self.name + ": " + self.handle.before )
Jon Halla4a79312022-01-25 17:16:53 -08001471 return self.checkPortForward( podName, portsList, kubeconfig, namespace )
Jon Hall06fd0df2021-01-25 15:50:06 -08001472 except Exception:
1473 main.log.exception( self.name + ": Uncaught exception!" )
1474 return main.FALSE
Daniele Moroe1d05eb2021-09-23 19:52:30 +02001475
Jon Halla16b4db2021-10-20 14:11:59 -07001476 def checkPortForward( self, podName, portsList=None, kubeconfig=None, namespace=None ):
1477 """
1478 Check that kubectl port-forward session is still active and restarts it if it was closed.
1479
1480
1481 Required Arguments:
1482 - podName: The name of the pod as a string
1483 - portsList: The list of ports to forward, as a string. see kubectl help for details. Deafults to
1484 the last used string on this node.
1485 Optional Arguments:
1486 - kubeconfig: The path to a kubeconfig file
1487 - namespace: The namespace to search in
1488 Returns main.TRUE if a port-forward session was created or is still active, main.FALSE on Error
1489
1490
1491 """
1492 try:
Jon Halldecf0982022-02-03 13:57:11 -08001493 main.log.debug( "%s: Checking port-forward session to %s" % ( self.name, podName ) )
Jon Halla16b4db2021-10-20 14:11:59 -07001494 if not portsList:
1495 portsList = self.portForwardList
1496 self.handle.sendline( "" )
1497 i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ], timeout=5 )
1498 output = self.handle.before + str( self.handle.after )
1499 main.log.debug( "%s: %s" % ( self.name, output ) )
1500 if i == 0:
1501 # We are not currently in a port-forwarding session, try to re-establish.
Jon Halldecf0982022-02-03 13:57:11 -08001502 main.log.warn( "%s: port-forwarding session to %s closed, attempting to reestablish." % ( self.name, podName ) )
Jon Halla16b4db2021-10-20 14:11:59 -07001503 return self.kubectlPortForward( podName, portsList, kubeconfig, namespace )
1504 elif i == 1:
Jon Halla4a79312022-01-25 17:16:53 -08001505 main.log.debug( "%s: We seem to still be in port-forwarding session" % self.name )
Jon Halla16b4db2021-10-20 14:11:59 -07001506 # Still in a command, port-forward is probably still active
1507 return main.TRUE
1508 except pexpect.EOF:
1509 main.log.error( self.name + ": EOF exception found" )
1510 main.log.error( self.name + ": " + self.handle.before )
1511 return main.FALSE
1512 except pexpect.TIMEOUT:
1513 main.log.exception( self.name + ": TIMEOUT exception found" )
1514 main.log.error( self.name + ": " + self.handle.before )
1515 return main.FALSE
1516 except Exception:
1517 main.log.exception( self.name + ": Uncaught exception!" )
1518 return main.FALSE
1519
Daniele Moro80271cb2021-11-11 20:08:51 +01001520 def kubectlSetLabel( self, nodeName, label, value, kubeconfig=None, namespace=None,
1521 timeout=240, overwrite=True ):
1522 try:
1523 cmdStr = "kubectl %s %s label node %s %s %s=%s" % (
1524 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1525 "-n %s" % namespace if namespace else "",
1526 nodeName, "--overwrite" if overwrite else "",
1527 label, value )
1528 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1529 self.handle.sendline( cmdStr )
1530 i = self.handle.expect( [ "error",
1531 "The connection to the server",
1532 "node/%s not labeled" % nodeName,
1533 "node/%s labeled" % nodeName, ],
1534 timeout=timeout )
1535 if i == 3 or i == 4:
1536 output = self.handle.before + self.handle.after
1537 main.log.debug( self.name + ": " + output )
1538 self.clearBuffer()
1539 return main.TRUE
1540 else:
1541 main.log.error( self.name + ": Error executing command" )
1542 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1543 self.clearBuffer()
1544 return main.FALSE
1545 except pexpect.EOF:
1546 main.log.error( self.name + ": EOF exception found" )
1547 main.log.error( self.name + ": " + self.handle.before )
1548 return main.FALSE
1549 except pexpect.TIMEOUT:
1550 main.log.exception( self.name + ": TIMEOUT exception found" )
1551 main.log.error( self.name + ": " + self.handle.before )
1552 self.clearBuffer()
1553 return main.FALSE
1554 except Exception:
1555 main.log.exception( self.name + ": Uncaught exception!" )
1556 return main.FALSE
1557
Jon Halla16b4db2021-10-20 14:11:59 -07001558 def kubectlCordonNode( self, nodeName, kubeconfig=None, namespace=None, timeout=240, uncordonOnDisconnect=True ):
1559 try:
1560 cmdStr = "kubectl %s %s cordon %s" % (
1561 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1562 "-n %s" % namespace if namespace else "",
1563 nodeName )
1564 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1565 if uncordonOnDisconnect:
1566 self.nodeName = nodeName
1567 if kubeconfig:
1568 self.kubeconfig = kubeconfig
1569 if namespace:
1570 self.namespace = namespace
1571 self.preDisconnect = self.kubectlUncordonNode
1572 self.handle.sendline( cmdStr )
1573 i = self.handle.expect( [ "not found", "error",
1574 "The connection to the server",
1575 "node/%s cordoned" % nodeName,
1576 "node/%s already cordoned" % nodeName, ],
1577 timeout=timeout )
1578 if i == 3 or i == 4:
1579 output = self.handle.before + self.handle.after
1580 main.log.debug( self.name + ": " + output )
1581 self.clearBuffer()
1582 return main.TRUE
1583 else:
1584 main.log.error( self.name + ": Error executing command" )
1585 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1586 self.clearBuffer()
1587 return main.FALSE
1588 except pexpect.EOF:
1589 main.log.error( self.name + ": EOF exception found" )
1590 main.log.error( self.name + ": " + self.handle.before )
1591 return main.FALSE
1592 except pexpect.TIMEOUT:
1593 main.log.exception( self.name + ": TIMEOUT exception found" )
1594 main.log.error( self.name + ": " + self.handle.before )
1595 self.clearBuffer()
1596 return main.FALSE
1597 except Exception:
1598 main.log.exception( self.name + ": Uncaught exception!" )
1599 return main.FALSE
1600
1601 def kubectlUncordonNode( self, nodeName=None, kubeconfig=None, namespace=None, timeout=240 ):
1602 try:
1603 if not nodeName:
1604 nodeName = getattr( self, "nodeName" )
1605 if not kubeconfig:
1606 kubeconfig = getattr( self, "kubeconfig", None )
1607 if not kubeconfig:
1608 namespace = getattr( self, "namespace", None )
1609 cmdStr = "kubectl %s %s uncordon %s" % (
1610 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1611 "-n %s" % namespace if namespace else "",
1612 nodeName )
1613 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1614 self.handle.sendline( cmdStr )
1615 i = self.handle.expect( [ "not found", "error",
1616 "The connection to the server",
1617 "node/%s uncordoned" % nodeName,
1618 "node/%s already uncordoned" % nodeName, ],
1619 timeout=timeout )
1620 if i == 3 or i == 4:
1621 output = self.handle.before + self.handle.after
1622 main.log.debug( self.name + ": " + output )
1623 self.clearBuffer()
1624 return main.TRUE
1625 else:
1626 main.log.error( self.name + ": Error executing command" )
1627 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1628 self.clearBuffer()
1629 return main.FALSE
1630 except pexpect.EOF:
1631 main.log.error( self.name + ": EOF exception found" )
1632 main.log.error( self.name + ": " + self.handle.before )
1633 return main.FALSE
1634 except pexpect.TIMEOUT:
1635 main.log.exception( self.name + ": TIMEOUT exception found" )
1636 main.log.error( self.name + ": " + self.handle.before )
1637 self.clearBuffer()
1638 return main.FALSE
1639 except Exception:
1640 main.log.exception( self.name + ": Uncaught exception!" )
1641 return main.FALSE
1642
Daniele Moroe1d05eb2021-09-23 19:52:30 +02001643 def kubectlDeletePod( self, podName, kubeconfig=None, namespace=None, timeout=240 ):
1644 try:
1645 cmdStr = "kubectl %s %s delete pod %s" % (
1646 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1647 "-n %s" % namespace if namespace else "",
1648 podName )
1649 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1650 self.handle.sendline( cmdStr )
1651 i = self.handle.expect( [ "not found", "error",
1652 "The connection to the server",
1653 self.prompt ],
1654 timeout=timeout )
1655 if i == 3:
1656 main.log.debug( self.name + ": " + self.handle.before )
1657 self.clearBuffer()
1658 return main.TRUE
1659 else:
1660 main.log.error( self.name + ": Error executing command" )
1661 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1662 self.clearBuffer()
1663 return main.FALSE
1664 except pexpect.EOF:
1665 main.log.error( self.name + ": EOF exception found" )
1666 main.log.error( self.name + ": " + self.handle.before )
1667 return main.FALSE
1668 except pexpect.TIMEOUT:
1669 main.log.exception( self.name + ": TIMEOUT exception found" )
1670 main.log.error( self.name + ": " + self.handle.before )
1671 return main.FALSE
1672 except Exception:
1673 main.log.exception( self.name + ": Uncaught exception!" )
1674 return main.FALSE
1675
1676 def kubectlCheckPodReady( self, podName, kubeconfig=None, namespace=None, timeout=240 ):
1677 try:
1678 cmdStr = "kubectl %s %s get pods " \
1679 "-o go-template='{{range $index, $element := .items}}{{range .status.containerStatuses}}{{if .ready}}{{$element.metadata.name}}{{\" ready\\n\"}}{{end}}{{end}}{{end}}' | grep --color=never %s" % (
1680 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1681 "-n %s" % namespace if namespace else "",
1682 podName )
1683 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1684 self.handle.sendline( cmdStr )
1685 # Since the command contains the prompt ($), we first expect for the
1686 # last part of the command and then we expect the actual values
Jon Halla16b4db2021-10-20 14:11:59 -07001687 self.handle.expect( "grep --color=never %s" % podName, timeout=1 )
Daniele Moroe1d05eb2021-09-23 19:52:30 +02001688 i = self.handle.expect( [ podName + " ready",
1689 self.prompt ],
1690 timeout=timeout )
1691 if i == 0:
1692 main.log.debug( self.name + ": " + podName + " ready" )
1693 self.clearBuffer()
1694 return main.TRUE
1695 else:
1696 main.log.error( self.name + ": Error executing command" )
1697 main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
1698 self.clearBuffer()
1699 return main.FALSE
1700 except pexpect.EOF:
1701 main.log.error( self.name + ": EOF exception found" )
1702 main.log.error( self.name + ": " + self.handle.before )
1703 return main.FALSE
1704 except pexpect.TIMEOUT:
1705 main.log.exception( self.name + ": TIMEOUT exception found" )
1706 main.log.error( self.name + ": " + self.handle.before )
1707 return main.FALSE
1708 except Exception:
1709 main.log.exception( self.name + ": Uncaught exception!" )
1710 return main.FALSE
1711
Jon Halla4a79312022-01-25 17:16:53 -08001712 def kubectlGetServiceIP( self, serviceName, kubeconfig=None, namespace=None, timeout=240 ):
1713 try:
1714 cmdStr = "kubectl %s %s get service %s " \
1715 "--output=jsonpath='{.spec.clusterIP}{\"\\n\"}'" % (
1716 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1717 "-n %s" % namespace if namespace else "",
1718 serviceName )
1719 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1720 self.handle.sendline( cmdStr )
1721 self.handle.expect( self.prompt, timeout=timeout )
1722 output = self.handle.before
1723 clusterIP = output.splitlines()
1724 main.log.debug( repr( clusterIP ) )
1725 return clusterIP[-2]
1726 except pexpect.EOF:
1727 main.log.error( self.name + ": EOF exception found" )
1728 main.log.error( self.name + ": " + self.handle.before )
1729 return None
1730 except pexpect.TIMEOUT:
1731 main.log.exception( self.name + ": TIMEOUT exception found" )
1732 main.log.error( self.name + ": " + self.handle.before )
1733 return None
1734 except Exception:
1735 main.log.exception( self.name + ": Uncaught exception!" )
1736 return None
1737
1738 def kubectlGetNodePort( self, serviceName, kubeconfig=None, namespace=None, timeout=240 ):
1739 try:
1740 cmdStr = "kubectl %s %s get service %s " \
1741 "--output=jsonpath='{.spec.ports[*].nodePort}{\"\\n\"}'" % (
1742 "--kubeconfig %s" % kubeconfig if kubeconfig else "",
1743 "-n %s" % namespace if namespace else "",
1744 serviceName )
1745 main.log.info( self.name + ": sending: " + repr( cmdStr ) )
1746 self.handle.sendline( cmdStr )
1747 self.handle.expect( self.prompt, timeout=timeout )
1748 output = self.handle.before
1749 clusterIP = output.splitlines()
1750 main.log.debug( repr( clusterIP ) )
1751 return clusterIP[-2]
1752 except pexpect.EOF:
1753 main.log.error( self.name + ": EOF exception found" )
1754 main.log.error( self.name + ": " + self.handle.before )
1755 return None
1756 except pexpect.TIMEOUT:
1757 main.log.exception( self.name + ": TIMEOUT exception found" )
1758 main.log.error( self.name + ": " + self.handle.before )
1759 return None
1760 except Exception:
1761 main.log.exception( self.name + ": Uncaught exception!" )
1762 return None
1763
Daniele Moroe1d05eb2021-09-23 19:52:30 +02001764 def clearBuffer(self):
1765 i = 0
1766 response = ''
1767 while True:
1768 try:
1769 i += 1
1770 self.handle.expect( self.prompt, timeout=5 )
1771 response += self.cleanOutput( self.handle.before )
1772 except pexpect.TIMEOUT:
Jon Halla16b4db2021-10-20 14:11:59 -07001773 return response