blob: 55f05baa55232e5ebae823a7ba7f4c9e6488e5af [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 Ronquillob27ce4c2017-07-17 12:41:28 -07004Copyright 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
kelvin8ec71442015-01-15 16:57:00 -080013 ( at your option ) any later version.
adminbae64d82013-08-01 10:50:15 -070014
15 TestON is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
kelvin8ec71442015-01-15 16:57:00 -080021 along with TestON. If not, see <http://www.gnu.org/licenses/>.
kelvin8ec71442015-01-15 16:57:00 -080022"""
adminbae64d82013-08-01 10:50:15 -070023import pexpect
kelvin8ec71442015-01-15 16:57:00 -080024import re
adminbae64d82013-08-01 10:50:15 -070025
26from drivers.component import Component
kelvin8ec71442015-01-15 16:57:00 -080027
28
29class CLI( Component ):
30
31 """
adminbae64d82013-08-01 10:50:15 -070032 This will define common functions for CLI included.
kelvin8ec71442015-01-15 16:57:00 -080033 """
34 def __init__( self ):
Devin Limdc78e202017-06-09 18:30:07 -070035 super( CLI, self ).__init__()
Jon Hallca319892017-06-15 15:25:22 -070036
Devin Limdc78e202017-06-09 18:30:07 -070037 def checkPrompt(self):
38 for key in self.options:
39 if key == "prompt" and self.options['prompt'] is not None:
40 self.prompt = self.options['prompt']
41 break
kelvin8ec71442015-01-15 16:57:00 -080042
43 def connect( self, **connectargs ):
44 """
adminbae64d82013-08-01 10:50:15 -070045 Connection will establish to the remote host using ssh.
46 It will take user_name ,ip_address and password as arguments<br>
kelvin8ec71442015-01-15 16:57:00 -080047 and will return the handle.
48 """
adminbae64d82013-08-01 10:50:15 -070049 for key in connectargs:
kelvin8ec71442015-01-15 16:57:00 -080050 vars( self )[ key ] = connectargs[ key ]
Devin Limdc78e202017-06-09 18:30:07 -070051 self.checkPrompt()
adminbae64d82013-08-01 10:50:15 -070052
kelvin8ec71442015-01-15 16:57:00 -080053 connect_result = super( CLI, self ).connect()
adminbae64d82013-08-01 10:50:15 -070054 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -080055 refused = "ssh: connect to host " + \
56 self.ip_address + " port 22: Connection refused"
adminbae64d82013-08-01 10:50:15 -070057 if self.port:
kelvin8ec71442015-01-15 16:57:00 -080058 self.handle = pexpect.spawn(
59 'ssh -p ' +
60 self.port +
61 ' ' +
62 self.user_name +
63 '@' +
64 self.ip_address,
Jon Hall9aaba882015-01-19 15:05:15 -080065 env={ "TERM": "xterm-mono" },
kelvin8ec71442015-01-15 16:57:00 -080066 maxread=50000 )
67 else:
68 self.handle = pexpect.spawn(
69 'ssh -X ' +
70 self.user_name +
71 '@' +
72 self.ip_address,
Jon Hall9aaba882015-01-19 15:05:15 -080073 env={ "TERM": "xterm-mono" },
kelvin8ec71442015-01-15 16:57:00 -080074 maxread=1000000,
75 timeout=60 )
adminbae64d82013-08-01 10:50:15 -070076
Jon Hall73057ee2016-08-23 09:57:26 -070077 # set tty window size
78 self.handle.setwinsize( 24, 250 )
79
adminbae64d82013-08-01 10:50:15 -070080 self.handle.logfile = self.logfile_handler
kelvin8ec71442015-01-15 16:57:00 -080081 i = 5
82 while i == 5:
83 i = self.handle.expect( [
acsmarse2be32c2015-06-29 16:16:28 -070084 ssh_newkey,
Jon Hall05f88682015-06-09 14:57:53 -070085 'password:|Password:',
kelvin8ec71442015-01-15 16:57:00 -080086 pexpect.EOF,
87 pexpect.TIMEOUT,
88 refused,
89 'teston>',
Devin Limdc78e202017-06-09 18:30:07 -070090 self.prompt ],
acsmars07f9d392015-07-15 10:30:58 -070091 120 )
acsmars32de0bc2015-06-30 09:57:12 -070092 if i == 0: # Accept key, then expect either a password prompt or access
kelvin8ec71442015-01-15 16:57:00 -080093 main.log.info( "ssh key confirmation received, send yes" )
94 self.handle.sendline( 'yes' )
acsmars32de0bc2015-06-30 09:57:12 -070095 i = 5 # Run the loop again
acsmars07f9d392015-07-15 10:30:58 -070096 continue
97 if i == 1: # Password required
Jon Hall63604932015-02-26 17:09:50 -080098 if self.pwd:
99 main.log.info(
acsmars07f9d392015-07-15 10:30:58 -0700100 "ssh connection asked for password, gave password" )
Jon Hall63604932015-02-26 17:09:50 -0800101 else:
acsmars07f9d392015-07-15 10:30:58 -0700102 main.log.info( "Server asked for password, but none was "
103 "given in the .topo file. Trying "
104 "no password.")
105 self.pwd = ""
106 self.handle.sendline( self.pwd )
107 j = self.handle.expect( [
Devin Limdc78e202017-06-09 18:30:07 -0700108 self.prompt,
acsmars07f9d392015-07-15 10:30:58 -0700109 'password:|Password:',
110 pexpect.EOF,
111 pexpect.TIMEOUT ],
112 120 )
113 if j != 0:
114 main.log.error( "Incorrect Password" )
115 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800116 elif i == 2:
117 main.log.error( "Connection timeout" )
118 return main.FALSE
119 elif i == 3: # timeout
120 main.log.error(
121 "No route to the Host " +
122 self.user_name +
123 "@" +
124 self.ip_address )
125 return main.FALSE
126 elif i == 4:
127 main.log.error(
128 "ssh: connect to host " +
129 self.ip_address +
130 " port 22: Connection refused" )
131 return main.FALSE
132 elif i == 6:
133 main.log.info( "Password not required logged in" )
adminbae64d82013-08-01 10:50:15 -0700134
kelvin8ec71442015-01-15 16:57:00 -0800135 self.handle.sendline( "" )
Devin Limdc78e202017-06-09 18:30:07 -0700136 self.handle.expect( self.prompt )
Jeremy Ronquillo0f2008a2017-06-23 15:32:51 -0700137 self.handle.sendline( "cd" )
138 self.handle.expect( self.prompt )
adminbae64d82013-08-01 10:50:15 -0700139 return self.handle
140
kelvin8ec71442015-01-15 16:57:00 -0800141 def disconnect( self ):
142 result = super( CLI, self ).disconnect( self )
adminbae64d82013-08-01 10:50:15 -0700143 result = main.TRUE
kelvin8ec71442015-01-15 16:57:00 -0800144 # self.execute( cmd="exit",timeout=120,prompt="(.*)" )
145
146 def execute( self, **execparams ):
147 """
adminbae64d82013-08-01 10:50:15 -0700148 It facilitates the command line execution of a given command. It has arguments as :
149 cmd => represents command to be executed,
150 prompt => represents expect command prompt or output,
151 timeout => timeout for command execution,
152 more => to provide a key press if it is on.
153
154 It will return output of command exection.
kelvin8ec71442015-01-15 16:57:00 -0800155 """
156 result = super( CLI, self ).execute( self )
adminaef00552014-05-08 09:18:36 -0700157 defaultPrompt = '.*[$>\#]'
Jon Hall3b489db2015-10-05 14:38:37 -0700158 args = utilities.parse_args( [ "CMD",
159 "TIMEOUT",
160 "PROMPT",
161 "MORE" ],
162 **execparams )
kelvin8ec71442015-01-15 16:57:00 -0800163
164 expectPrompt = args[ "PROMPT" ] if args[ "PROMPT" ] else defaultPrompt
adminbae64d82013-08-01 10:50:15 -0700165 self.LASTRSP = ""
kelvin8ec71442015-01-15 16:57:00 -0800166 timeoutVar = args[ "TIMEOUT" ] if args[ "TIMEOUT" ] else 10
adminbae64d82013-08-01 10:50:15 -0700167 cmd = ''
kelvin8ec71442015-01-15 16:57:00 -0800168 if args[ "CMD" ]:
169 cmd = args[ "CMD" ]
170 else:
adminbae64d82013-08-01 10:50:15 -0700171 return 0
kelvin8ec71442015-01-15 16:57:00 -0800172 if args[ "MORE" ] is None:
173 args[ "MORE" ] = " "
174 self.handle.sendline( cmd )
adminbae64d82013-08-01 10:50:15 -0700175 self.lastCommand = cmd
Jon Hall3b489db2015-10-05 14:38:37 -0700176 index = self.handle.expect( [ expectPrompt,
177 "--More--",
178 'Command not found.',
179 pexpect.TIMEOUT,
180 "^:$" ],
181 timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700182 if index == 0:
kelvin8ec71442015-01-15 16:57:00 -0800183 self.LASTRSP = self.LASTRSP + \
184 self.handle.before + self.handle.after
Jon Hall3b489db2015-10-05 14:38:37 -0700185 main.log.info( "Executed :" + str(cmd ) +
186 " \t\t Expected Prompt '" + str( expectPrompt) +
187 "' Found" )
adminbae64d82013-08-01 10:50:15 -0700188 elif index == 1:
189 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800190 self.handle.send( args[ "MORE" ] )
191 main.log.info(
192 "Found More screen to go , Sending a key to proceed" )
193 indexMore = self.handle.expect(
194 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700195 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800196 main.log.info(
197 "Found anoother More screen to go , Sending a key to proceed" )
198 self.handle.send( args[ "MORE" ] )
199 indexMore = self.handle.expect(
200 [ "--More--", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700201 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800202 elif index == 2:
203 main.log.error( "Command not found" )
adminbae64d82013-08-01 10:50:15 -0700204 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800205 elif index == 3:
Jon Hall3b489db2015-10-05 14:38:37 -0700206 main.log.error( "Expected Prompt not found, Time Out!!" )
kelvin8ec71442015-01-15 16:57:00 -0800207 main.log.error( expectPrompt )
Jon Hall3b489db2015-10-05 14:38:37 -0700208 self.LASTRSP = self.LASTRSP + self.handle.before
209 return self.LASTRSP
adminbae64d82013-08-01 10:50:15 -0700210 elif index == 4:
211 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800212 # self.handle.send( args[ "MORE" ] )
213 self.handle.sendcontrol( "D" )
214 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700215 "Found More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800216 indexMore = self.handle.expect(
217 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700218 while indexMore == 0:
kelvin8ec71442015-01-15 16:57:00 -0800219 main.log.info(
Jon Hall3b489db2015-10-05 14:38:37 -0700220 "Found another More screen to go, Sending a key to proceed" )
kelvin8ec71442015-01-15 16:57:00 -0800221 self.handle.sendcontrol( "D" )
222 indexMore = self.handle.expect(
223 [ "^:$", expectPrompt ], timeout=timeoutVar )
adminbae64d82013-08-01 10:50:15 -0700224 self.LASTRSP = self.LASTRSP + self.handle.before
kelvin8ec71442015-01-15 16:57:00 -0800225 main.last_response = self.remove_contol_chars( self.LASTRSP )
adminbae64d82013-08-01 10:50:15 -0700226 return self.LASTRSP
kelvin8ec71442015-01-15 16:57:00 -0800227
228 def remove_contol_chars( self, response ):
229 # 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 ) )
230 # response = re.sub( RE_XML_ILLEGAL, "\n", response )
231 response = re.sub( r"[\x01-\x1F\x7F]", "", response )
232 # response = re.sub( r"\[\d+\;1H", "\n", response )
233 response = re.sub( r"\[\d+\;\d+H", "", response )
adminbae64d82013-08-01 10:50:15 -0700234 return response
adminbae64d82013-08-01 10:50:15 -0700235
kelvin8ec71442015-01-15 16:57:00 -0800236 def runAsSudoUser( self, handle, pwd, default ):
237
238 i = handle.expect( [ ".ssword:*", default, pexpect.EOF ] )
239 if i == 0:
240 handle.sendline( pwd )
Jon Hall5ec6b1b2015-09-17 18:20:14 -0700241 handle.sendline( "\n" )
kelvin8ec71442015-01-15 16:57:00 -0800242
243 if i == 1:
244 handle.expect( default )
245
246 if i == 2:
247 main.log.error( "Unable to run as Sudo user" )
248
adminbae64d82013-08-01 10:50:15 -0700249 return handle
adminbae64d82013-08-01 10:50:15 -0700250
kelvin8ec71442015-01-15 16:57:00 -0800251 def onfail( self ):
252 if 'onfail' in main.componentDictionary[ self.name ]:
253 commandList = main.componentDictionary[
254 self.name ][ 'onfail' ].split( "," )
255 for command in commandList:
256 response = self.execute(
257 cmd=command,
258 prompt="(.*)",
259 timeout=120 )
adminbae64d82013-08-01 10:50:15 -0700260
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700261 def secureCopy( self, userName, ipAddress, filePath, dstPath, pwd="",
262 direction="from" ):
kelvin8ec71442015-01-15 16:57:00 -0800263 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700264 Definition:
265 Execute scp command in linux to copy to/from a remote host
266 Required:
267 str userName - User name of the remote host
268 str ipAddress - IP address of the remote host
269 str filePath - File path including the file it self
270 str dstPath - Destination path
271 Optional:
272 str pwd - Password of the host
273 str direction - Direction of the scp, default to "from" which means
274 copy "from" the remote machine to local machine,
275 while "to" means copy "to" the remote machine from
276 local machine
kelvin8ec71442015-01-15 16:57:00 -0800277 """
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700278 returnVal = main.TRUE
adminbae64d82013-08-01 10:50:15 -0700279 ssh_newkey = 'Are you sure you want to continue connecting'
kelvin8ec71442015-01-15 16:57:00 -0800280 refused = "ssh: connect to host " + \
Jon Hall547e0582015-09-21 17:35:40 -0700281 ipAddress + " port 22: Connection refused"
acsmars07f9d392015-07-15 10:30:58 -0700282
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700283 if direction == "from":
284 cmd = 'scp ' + str( userName ) + '@' + str( ipAddress ) + ':' + \
Jon Hall547e0582015-09-21 17:35:40 -0700285 str( filePath ) + ' ' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700286 elif direction == "to":
287 cmd = 'scp ' + str( filePath ) + ' ' + str( userName ) + \
Jon Hall547e0582015-09-21 17:35:40 -0700288 '@' + str( ipAddress ) + ':' + str( dstPath )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700289 else:
290 main.log.debug( "Wrong direction using secure copy command!" )
291 return main.FALSE
kelvin8ec71442015-01-15 16:57:00 -0800292
293 main.log.info( "Sending: " + cmd )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700294 self.handle.sendline( cmd )
Jon Hall547e0582015-09-21 17:35:40 -0700295 i = 0
296 while i < 2:
297 i = self.handle.expect( [
298 ssh_newkey,
299 'password:',
300 "100%",
301 refused,
302 "No such file or directory",
Jon Hall53c5e662016-04-13 16:06:56 -0700303 "Permission denied",
Devin Limdc78e202017-06-09 18:30:07 -0700304 self.prompt,
Jon Hall547e0582015-09-21 17:35:40 -0700305 pexpect.EOF,
306 pexpect.TIMEOUT ],
307 120 )
Jon Hall547e0582015-09-21 17:35:40 -0700308 if i == 0: # ask for ssh key confirmation
309 main.log.info( "ssh key confirmation received, sending yes" )
310 self.handle.sendline( 'yes' )
311 elif i == 1: # Asked for ssh password
312 main.log.info( "ssh connection asked for password, gave password" )
313 self.handle.sendline( pwd )
314 elif i == 2: # File finished transfering
315 main.log.info( "Secure copy successful" )
316 returnVal = main.TRUE
317 elif i == 3: # Connection refused
318 main.log.error(
319 "ssh: connect to host " +
320 ipAddress +
321 " port 22: Connection refused" )
322 returnVal = main.FALSE
323 elif i == 4: # File Not found
324 main.log.error( "No such file found" )
325 returnVal = main.FALSE
Jon Hall53c5e662016-04-13 16:06:56 -0700326 elif i == 5: # Permission denied
327 main.log.error( "Permission denied. Check folder permissions" )
328 returnVal = main.FALSE
329 elif i == 6: # prompt returned
330 return returnVal
331 elif i == 7: # EOF
Jon Hall547e0582015-09-21 17:35:40 -0700332 main.log.error( "Pexpect.EOF found!!!" )
Devin Lim44075962017-08-11 10:56:37 -0700333 main.cleanAndExit()
Jon Hall53c5e662016-04-13 16:06:56 -0700334 elif i == 8: # timeout
Jon Hall547e0582015-09-21 17:35:40 -0700335 main.log.error(
336 "No route to the Host " +
337 userName +
338 "@" +
339 ipAddress )
340 returnVal = main.FALSE
Devin Limdc78e202017-06-09 18:30:07 -0700341 self.handle.expect( self.prompt )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700342 return returnVal
343
344 def scp( self, remoteHost, filePath, dstPath, direction="from" ):
345 """
346 Definition:
347 Execute scp command in linux to copy to/from a remote host
348 Required:
349 * remoteHost - Test ON component to be parsed
350 str filePath - File path including the file it self
351 str dstPath - Destination path
352 Optional:
353 str direction - Direction of the scp, default to "from" which means
354 copy "from" the remote machine to local machine,
355 while "to" means copy "to" the remote machine from
356 local machine
357 """
358 return self.secureCopy( remoteHost.user_name,
359 remoteHost.ip_address,
360 filePath,
361 dstPath,
362 pwd=remoteHost.pwd,
363 direction=direction )
Devin Lim142b5342017-07-20 15:22:39 -0700364
365 def sshToNode( self, ipAddress, uName="sdn", pwd="rocks" ):
366 ssh_newkey = 'Are you sure you want to continue connecting'
367 refused = "ssh: connect to host " + ipAddress + " port 22: Connection refused"
368 handle = pexpect.spawn( 'ssh -X ' +
369 uName +
370 '@' +
371 ipAddress,
372 env={ "TERM": "xterm-mono" },
373 maxread=1000000,
374 timeout=60 )
375
376 # set tty window size
377 handle.setwinsize( 24, 250 )
378
379 i = 5
380 while i == 5:
381 i = handle.expect( [
382 ssh_newkey,
383 'password:|Password:',
384 pexpect.EOF,
385 pexpect.TIMEOUT,
386 refused,
387 'teston>',
388 self.prompt ],
389 120 )
390 if i == 0: # Accept key, then expect either a password prompt or access
391 main.log.info( "ssh key confirmation received, send yes" )
392 handle.sendline( 'yes' )
393 i = 5 # Run the loop again
394 continue
395 if i == 1: # Password required
396 if pwd:
397 main.log.info(
398 "ssh connection asked for password, gave password" )
399 else:
400 main.log.info( "Server asked for password, but none was "
401 "given in the .topo file. Trying "
402 "no password.")
403 pwd = ""
404 handle.sendline( pwd )
405 j = handle.expect( [ self.prompt,
406 'password:|Password:',
407 pexpect.EOF,
408 pexpect.TIMEOUT ],
409 120 )
410 if j != 0:
411 main.log.error( "Incorrect Password" )
Devin Lim44075962017-08-11 10:56:37 -0700412 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700413 elif i == 2:
414 main.log.error( "Connection timeout" )
Devin Lim44075962017-08-11 10:56:37 -0700415 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700416 elif i == 3: # timeout
417 main.log.error(
418 "No route to the Host " +
419 uName +
420 "@" +
421 ipAddress )
Devin Lim44075962017-08-11 10:56:37 -0700422 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700423 elif i == 4:
424 main.log.error(
425 "ssh: connect to host " +
426 ipAddress +
427 " port 22: Connection refused" )
Devin Lim44075962017-08-11 10:56:37 -0700428 main.cleanAndExit()
Devin Lim142b5342017-07-20 15:22:39 -0700429 elif i == 6:
430 main.log.info( "Password not required logged in" )
431
432 handle.sendline( "" )
433 handle.expect( self.prompt )
434 handle.sendline( "cd" )
435 handle.expect( self.prompt )
436
437 main.log.info ( "Successfully ssh to " + ipAddress + "." )
438 return handle
439
440 def exitFromSsh( self, handle, ipAddress ):
Devin Lim142b5342017-07-20 15:22:39 -0700441 try:
Jon Hall4f360bc2017-09-07 10:19:52 -0700442 handle.sendline( "logout" )
Devin Lim142b5342017-07-20 15:22:39 -0700443 handle.expect( "closed." )
444 main.log.info ( "Successfully closed ssh connection from " + ipAddress )
445 except pexpect.EOF:
446 main.log.error( "Failed to close the connection from " + ipAddress )
Jon Hall4f360bc2017-09-07 10:19:52 -0700447 try:
448 # check that this component handle still works
449 self.handle.sendline( "" )
450 self.handle.expect( self.prompt )
451 except pexpect.EOF:
452 main.log.error( self.handle.before )
453 main.log.error( "EOF after closing ssh connection" )