Paramiko no puede descargar archivos de gran tamaño> 1 GB

def download(): if os.path.exists( dst_dir_path ) == False: logger.error( "Cannot access destination folder %s. Please check path and permissions. " % ( dst_dir_path )) return 1 elif os.path.isdir( dst_dir_path ) == False: logger.error( "%s is not a folder. Please check path. " % ( dst_dir_path )) return 1 file_list = None #transport = paramiko.Transport(( hostname, port)) paramiko.util.log_to_file('paramiko.log') ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #transport try: ssh.connect( hostname, username=username, password=password, timeout=5.0) #transport.connect(username=username, password=password ) except Exception, err: logger.error( "Failed to connect to the remote server. Reason: %s" % ( str(err) ) ) return 1 try: #sftp = paramiko.SFTPClient.from_transport(transport) sftp = ssh.open_sftp() except Exception, err: logger.error( "Failed to start SFTP session from connection to %s. Check that SFTP service is running and available. Reason: %s" % ( hostname, str(err) )) return 1 try: sftp.chdir(src_dir_path) #file_list = sftp.listdir(path="%s" % ( src_dir_path ) ) file_list = sftp.listdir() except Exception, err: logger.error( "Failed to list files in folder %s. Please check path and permissions. Reason: %s" % ( src_dir_path, str(err) )) return 1 match_text = re.compile( file_mask ) download_count = 0 for file in file_list: # Here is an item name... but is it a file or directory? #logger.info( "Downloading file %s." % ( file ) ) if not re.match( file_mask, file ): continue else: logger.info( "File \"%s\" name matched file mask \"%s\". matches %s.Processing file..." % ( file, file_mask, (match_text.match( file_mask ) ) ) ) src_file_path = "./%s" % ( file ) dst_file_path = "/".join( [ dst_dir_path, file] ) retry_count = 0 while True: try: logger.info( "Downloading file %s to %s." % ( file, dst_file_path ) ) #sftp.get( file, dst_file_path, callback=printTotals ) #sftp.get( remote file, local file ) sftp.get( file, dst_file_path) #sftp.get( remote file, local file ) logger.info( "Successfully downloaded file %s to %s." % ( file, dst_file_path ) ) download_count += 1 break except Exception, err: if retry_count == retry_threshold: logger.error( "Failed to download %s to %s. Reason: %s." % ( file, dst_file_path, str(err) ) ) sftp.close() #transport.close() return 1 else: logger.error( "Failed to download %s to %s. Reason: %s." % ( file, dst_file_path, str(err) ) ) retry_count +=1 sftp.close() transport.close() logger.info( "%d files downloaded." % ( download_count ) ) return 0 

Cuando ejecuto la siguiente función, descarga el archivo de origen durante aproximadamente 3 minutos y luego cierra la sesión, aunque solo se han descargado 38-41MB (varía) de un archivo de 1-1.6GB.

Desde el archivo de registro de Paramiko, parece que la conexión SSh permanece abierta mientras se cierra la sesión SFTP:

DEB [20120913-10: 05: 00.894] thr = 1 paramiko.transport: Cambiar a nuevas claves … DEB [20120913-10: 05: 06.953] thr = 1 paramiko.transport: Rekeying (golpee 401 paquetes, 1053444 bytes recibidos ) DEB [20120913-10: 05: 07.391] thr = 1 paramiko.transport: kex algos: [‘diffie-hellman-group1-sha1’, ‘diffie-hellman-group-exchange-sha1’] clave del servidor: [‘ssh -dss ‘] cifrado del cliente: [‘ aes256-ctr ‘,’ aes192-ctr ‘,’ aes128-ctr ‘,’ aes256-cbc ‘,’ aes192-cbc ‘,’ aes128-cbc ‘,’ twofish-cbc ‘, ‘blowfish-cbc’, ‘3des-cbc’, ‘arcfour’] server encrypt: [‘aes256-ctr’, ‘aes192-ctr’, ‘aes128-ctr’, ‘aes256-cbc’, ‘aes192-cbc’, ‘aes128-cbc’, ‘twofish-cbc’, ‘blowfish-cbc’, ‘3des-cbc’, ‘arcfour’] cliente mac: [‘hmac-sha1’, ‘hmac-sha1-96’, ‘hmac-md5 ‘,’ hmac-md5-96 ‘,’ umac-64@openssh.com ‘] server mac: [‘ hmac-sha1 ‘,’ hmac-sha1-96 ‘,’ hmac-md5 ‘,’ hmac-md5-96 ‘,’ umac-64@openssh.com ‘] compresión del cliente: [‘ zlib@openssh.com ‘,’ zlib ‘,’ ninguna ‘] compresión del servidor: [‘ zlib@openssh.com ‘,’ zlib ‘,’ ninguna ‘] cliente lang: [‘ ‘] idioma del servidor: [‘ ‘] kex sigue? False DEB [20120913- 10: 05: 07.421] thr = 1 paramiko.transport: Cifras acordadas: local = aes128-ctr, remote = aes128-ctr DEB [20120913-10: 05: 07.421] thr = 1 paramiko.transport: utilizando kex diffie-hellman- grupo1-sha1; tipo de clave de servidor ssh-dss; cifrado: local aes128-ctr, remoto aes128-ctr; mac: local hmac-sha1, hmac-sha1 remoto; compresión: local ninguno, remoto ninguno DEB [20120913-10: 05: 07.625] thr = 1 paramiko.transport: Cambiar a nuevas teclas … INF [20120913-10: 05: 10.374] thr = 2 paramiko.transport.sftp: [chan 1] sesión sftp cerrada. DEB [20120913-10: 05: 10.388] thr = 2 paramiko.transport: [chan 1] EOF enviado (1)

Después de este punto, la secuencia de comandos se cierra con esta excepción (del bloque try / except sftp.get ())

No hay recursos suficientes para completar la solicitud.

El sistema en sí tiene gigabytes de espacio libre en disco, por lo que ese no es el problema.

La misma transferencia en la que falla la parakmiko funciona bien con FileZilla y con la aplicación Java que escribí hace años para hacer transferencias de SFTP. Así que creo que es un problema con paramiko.

Esto se está ejecutando en Windows XP y en Windows Server 2003.

He intentado parchear Paramko 1.17 para que actualice las teclas con más frecuencia, pero la transferencia todavía produce un error. Python 2.7.3 Paramiko 1.7 con parche Windows 2003 Sevfer

Ideas?

Información adicional: Falla en el servidor de Windows XP SP3 y Windows 2003, exactamente el mismo comportamiento y mensajes de error. Información de sys.version Windows XP Workstation: ‘2.7.3 (predeterminado, 10 de abril de 2012, 23:31:26) [MSC v.1500 32 bit (Intel)]’ Windows 2003 Server: ‘2.7.3 (predeterminada, 10 de abril 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] ‘Parché el archivo packet.py para disminuir el tiempo entre renovaciones de claves. No tuvo ningún efecto en el comportamiento de sftp.get ().

El protocolo SFTP no tiene una manera de transmitir datos de archivos; en su lugar, lo que tiene es una forma de solicitar un bloque de datos de un desplazamiento particular en un archivo abierto. El método ingenuo de descargar un archivo sería solicitar el primer bloque, escribirlo en el disco, luego solicitar el segundo bloque, etc. Esto es confiable, pero muy lento.

En su lugar, Paramiko tiene un truco de rendimiento que utiliza: cuando llama a .get() , envía inmediatamente una solicitud para cada bloque en el archivo, y recuerda en qué desplazamiento se supone que se deben escribir. Luego, a medida que llega cada respuesta, se asegura de que se escriba en el disco en el desplazamiento correcto. Para obtener más información, consulte los SFTPFile.prefetch() y SFTPFile.readv() en la documentación de Paramiko. Sospecho que la información de contabilidad que almacena cuando descarga su archivo de 1GB podría estar causando … que se le agoten los recursos, generando su mensaje de “recursos insuficientes”.

En lugar de usar .get() , si solo llama a .open() para obtener una instancia de SFTPFile , luego llame a .read() en ese objeto, o simplemente entréguelo a la función de la biblioteca estándar de Python shutil.copyfileobj() para descargar contenido. Eso debería evitar el caché de recuperación previa de Paramiko y permitirte descargar el archivo aunque no sea tan rápido.

es decir:

  def lazy_loading_ftp_file(sftp_host_conn, filename): """ Lazy loading ftp file when exception simple sftp.get call :param sftp_host_conn: sftp host :param filename: filename to be downloaded :return: None, file will be downloaded current directory """ import shutil try: with sftp_host_conn() as host: sftp_file_instance = host.open(filename, 'r') with open(filename, 'wb') as out_file: shutil.copyfileobj(sftp_file_instance, out_file) return {"status": "sucess", "msg": "sucessfully downloaded file: {}".format(filename)} except Exception as ex: return {"status": "failed", "msg": "Exception in Lazy reading too: {}".format(ex)} 

Tuve un problema muy similar, en mi caso el archivo solo tiene unos 400 MB, pero fallaría constantemente después de descargar unos 35 MB aproximadamente. No siempre fallaba en el mismo número exacto de bytes descargados, pero en algún lugar del orden de los 35 a 40 MB, el archivo dejaría de transferirse y un minuto después obtendría el error “No hay recursos suficientes para completar la solicitud”.

Descargar el archivo a través de WinSCP o PSFTP funcionó bien.

Probé el método de Screwtape, y funcionó, pero fue muy lento. Mi archivo de 400 MB estaba a punto de demorar aproximadamente 4 horas en descargarse, lo cual era un período inaceptable para esta aplicación en particular.

Además, en un momento, cuando configuramos esto por primera vez, todo funcionó bien. Pero el administrador del servidor hizo algunos cambios en el servidor SFTP y fue entonces cuando las cosas se rompieron. No estoy seguro de cuáles fueron los cambios, pero como todavía funcionaba bien usando WinSCP / otros métodos SFTP, no pensé que sería fructífero intentar atacar esto desde el lado del servidor.

No voy a pretender entender por qué, pero esto es lo que terminó funcionando para mí:

  1. Descargué e instalé la versión actual de Paramiko (1.11.1 en este momento). Inicialmente, esto no hizo ninguna diferencia en absoluto, pero pensé que lo mencionaría en caso de que fuera parte de la solución.

  2. La traza de la stack para la excepción fue:

     File "C:\Python26\lib\site-packages\paramiko\sftp_client.py", line 676, in get size = self.getfo(remotepath, fl, callback) File "C:\Python26\lib\site-packages\paramiko\sftp_client.py", line 645, in getfo data = fr.read(32768) File "C:\Python26\lib\site-packages\paramiko\file.py", line 153, in read new_data = self._read(read_size) File "C:\Python26\lib\site-packages\paramiko\sftp_file.py", line 157, in _read data = self._read_prefetch(size) File "C:\Python26\lib\site-packages\paramiko\sftp_file.py", line 138, in _read_prefetch self._check_exception() File "C:\Python26\lib\site-packages\paramiko\sftp_file.py", line 483, in _check_exception raise x 
  3. Hurgando un poco en sftp_file.py, noté esto (líneas 43-45 en la versión actual):

     # Some sftp servers will choke if you send read/write requests larger than # this size. MAX_REQUEST_SIZE = 32768 
  4. Por un capricho, intenté cambiar MAX_REQUEST_SIZE a 1024 y, he aquí, pude descargar el archivo completo.

  5. Después de que funcionó cambiando el MAX_REQUEST_SIZE a 1024, probé un montón de otros valores entre 1024 y 32768 para ver si afectaba el rendimiento o algo así. Pero siempre recibí el error, tarde o temprano, cuando el valor era significativamente mayor que 1024 (1025 estaba bien, pero 1048 finalmente falló).

Además de la respuesta de Screwtape, también vale la pena mencionar que probablemente debería limitar el tamaño del bloque con .read([block size in bytes])

Ver método perezoso para leer archivo grande

Tuve problemas reales con solo file.read() sin un tamaño de bloque en 2.4, aunque es posible que 2.7 determine el tamaño de bloque correcto.

Intenté rastrear el código en paramiko, ahora estoy seguro de que es el problema del servidor.

1. Lo que prefetch ha hecho

Para boost la velocidad de descarga, paramiko intenta la captura SFTP_FILE.prefetch() del archivo mediante el método de recuperación. SFTP_FILE.prefetch() se llama al método SFTP_FILE.prefetch() , se crea un nuevo subproceso y se envía una solicitud de recuperación de toneladas al servidor para que se cubra todo el archivo.
Podemos encontrar esto en el archivo paramiko / sftp_file.py alrededor de la línea 464.

2. ¿Cómo es el problema del servidor?

La solicitud mencionada anteriormente se ejecuta en modo asíncrono. SFTP_FILE._async_response() se usa para recibir la respuesta del servidor async.Y rastrear el código, podemos encontrar que esta excepción se crea en el método SFTP_FILE._async_response() que se convierte del mensaje enviado desde el servidor. Ahora, podemos estar seguros de que es la excepción del servidor.

3. ¿Cómo resolver el problema?

Porque no tengo acceso al servidor, por lo que usar sftp en la línea de comandos es mi mejor opción. Pero, por otro lado, ahora sabemos que demasiadas solicitudes hacen que el servidor se bloquee, por lo que podemos suspenderlo cuando enviamos la solicitud al servidor. .