多个客户的Paramiko ssh.close()问题

时间:2019-04-29 22:12:05

标签: python-3.6 paramiko

我与Paramiko有一个有趣的问题。我在我的代码中将它用于SSHClient,我先在其中测试连接,然后再连接(始终连接到同一主机),最后执行任务。除了在一种特定情况下,所有功能都运行良好,即使同时运行也是如此。假设我有2个任务,第一个完成2分钟,第二个完成5分钟。如果我同时运行它们,但仅按此顺序运行-我首先启动2分钟的任务,然后在运行该任务时我启动5分钟的任务;完成2分钟的任务后,两项任务均已完成,也就是5分钟的任务被终止。我怀疑问题出在ssh.close()上,它关闭了两个会话以及它们的底层传输。我确实使用线程并将SSH函数作为单独的线程调用,但这似乎并没有太大的区别。

这是一个简化的代码:

connect.py:

import paramiko


HOST = "test.example.com"
USER = "test"
KEY = "/root/.ssh/id_rsa"

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

def test_ssh():
  try:
      ssh.connect(HOST, username=USER, key_filename=KEY)
  except Exception as e:
      raise ValueError("Connection Error: {}".format(str(e)))
      ssh.close()

def run_ssh(cmd, logname):
  file = open(logname, 'w')
  stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
  for line in iter(stdout.readline, ''):
    file.write(''.join(line))
  file.close()
  ssh.close()

app.py:

import threading
from flask import jsonify
from connect import test_ssh, run_ssh
import time

min2 ='/home/test/2min.sh'
log2 = '2minuteoutput.log'
min5 ='/home/test/5min.sh'
log5 = '5minuteoutput.log'

def run(cmd, logname):
  try:
    test_ssh()
  except ValueError as e:
    return jsonify({"status": "error", 'message' : str(e)})
  else:
    somethread = threading.Thread(target=run_ssh, args=(cmd, logname))
    somethread.start()

run(min2, log2)
time.sleep(5)
run(min5, log5)

在另一台主机上,我放置了两个正在执行的shell脚本:

/home/test/2min.sh(由测试用户拥有并具有可执行文件):

#!/bin/bash
echo "Starting 2 minute job at: "`date`
sleep 60
echo "2min job min1" `date`
sleep 60
echo "2min job min2" `date`
echo "Completed 2 minute job at:" `date`

/home/test/5min.sh:

#!/bin/bash
echo "Starting 5 minute job at: "`date`
sleep 60
echo " 5 min job: min1" `date`
sleep 60
echo "5min job: min2" `date`
sleep 60
echo "5 min job: min3" `date`
sleep 60
echo "5min job min4" `date`
sleep 60
echo "5min job min5" `date`
echo "Completed 5 minute job at:" `date`

然后我将其作为python app.py执行,输出日志如下所示:

2minuteoutout.log:

Starting 2 minute job at: Tue Apr 30 20:09:06 UTC 2019
2min job min1 Tue Apr 30 20:10:06 UTC 2019
2min job min2 Tue Apr 30 20:11:06 UTC 2019
Completed 2 minute job at: Tue Apr 30 20:11:06 UTC 2019

5minuteoutput.log:

Starting 5 minute job at: Tue Apr 30 20:09:11 UTC 2019
 5 min job: min1 Tue Apr 30 20:10:11 UTC 2019

当第一个作业调用ssh.close()时,两个作业都在UTC时间20:11:06结束。

这也是paramiko调试日志的输出:

DEB [20190430-21:20:26.906] thr=1   paramiko.transport: starting thread (client mode): 0xd6ed09b0
DEB [20190430-21:20:26.907] thr=1   paramiko.transport: Local version/idstring: SSH-2.0-paramiko_2.4.2
DEB [20190430-21:20:26.996] thr=1   paramiko.transport: Remote version/idstring: SSH-2.0-OpenSSH_7.4
INF [20190430-21:20:26.997] thr=1   paramiko.transport: Connected (version 2.0, client OpenSSH_7.4)
DEB [20190430-21:20:27.085] thr=1   paramiko.transport: kex algos:['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512
DEB [20190430-21:20:27.085] thr=1   paramiko.transport: Kex agreed: ecdh-sha2-nistp256
DEB [20190430-21:20:27.085] thr=1   paramiko.transport: HostKey agreed: ssh-ed25519
DEB [20190430-21:20:27.085] thr=1   paramiko.transport: Cipher agreed: aes128-ctr
DEB [20190430-21:20:27.085] thr=1   paramiko.transport: MAC agreed: hmac-sha1
DEB [20190430-21:20:27.086] thr=1   paramiko.transport: Compression agreed: none
DEB [20190430-21:20:27.215] thr=1   paramiko.transport: kex engine KexNistp256 specified hash_algo <built-in function openssl_sha256>
DEB [20190430-21:20:27.216] thr=1   paramiko.transport: Switch to new keys ...
DEB [20190430-21:20:27.216] thr=2   paramiko.transport: Adding ssh-ed25519 host key for test.example.com: 
DEB [20190430-21:20:27.217] thr=2   paramiko.transport: Trying discovered key in /root/.ssh/id_rsa
DEB [20190430-21:20:27.299] thr=1   paramiko.transport: userauth is OK
INF [20190430-21:20:27.399] thr=1   paramiko.transport: Authentication (publickey) successful!
DEB [20190430-21:20:27.400] thr=3   paramiko.transport: [chan 0] Max packet in: 32768 bytes
DEB [20190430-21:20:27.484] thr=1   paramiko.transport: Received global request "hostkeys-00@openssh.com"
DEB [20190430-21:20:27.484] thr=1   paramiko.transport: Rejecting "hostkeys-00@openssh.com" global request from server.
DEB [20190430-21:20:27.604] thr=1   paramiko.transport: [chan 0] Max packet out: 32768 bytes
DEB [20190430-21:20:27.604] thr=1   paramiko.transport: Secsh channel 0 opened.
DEB [20190430-21:20:27.683] thr=1   paramiko.transport: [chan 0] Sesch channel 0 request ok
DEB [20190430-21:20:27.767] thr=1   paramiko.transport: [chan 0] Sesch channel 0 request ok
DEB [20190430-21:20:32.491] thr=4   paramiko.transport: starting thread (client mode): 0xd14075c0
DEB [20190430-21:20:32.492] thr=4   paramiko.transport: Local version/idstring: SSH-2.0-paramiko_2.4.2
DEB [20190430-21:20:32.579] thr=4   paramiko.transport: Remote version/idstring: SSH-2.0-OpenSSH_7.4
INF [20190430-21:20:32.579] thr=4   paramiko.transport: Connected (version 2.0, client OpenSSH_7.4)
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: kex algos:['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: Kex agreed: ecdh-sha2-nistp256
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: HostKey agreed: ssh-ed25519
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: Cipher agreed: aes128-ctr
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: MAC agreed: hmac-sha1
DEB [20190430-21:20:32.667] thr=4   paramiko.transport: Compression agreed: none
DEB [20190430-21:20:32.756] thr=4   paramiko.transport: kex engine KexNistp256 specified hash_algo <built-in function openssl_sha256>
DEB [20190430-21:20:32.757] thr=4   paramiko.transport: Switch to new keys ...
DEB [20190430-21:20:32.758] thr=2   paramiko.transport: Trying discovered key in /root/.ssh/id_rsa
DEB [20190430-21:20:32.841] thr=4   paramiko.transport: userauth is OK
INF [20190430-21:20:32.932] thr=4   paramiko.transport: Authentication (publickey) successful!
DEB [20190430-21:20:32.933] thr=5   paramiko.transport: [chan 0] Max packet in: 32768 bytes
DEB [20190430-21:20:33.016] thr=4   paramiko.transport: Received global request "hostkeys-00@openssh.com"
DEB [20190430-21:20:33.016] thr=4   paramiko.transport: Rejecting "hostkeys-00@openssh.com" global request from server.
DEB [20190430-21:20:33.137] thr=4   paramiko.transport: [chan 0] Max packet out: 32768 bytes
DEB [20190430-21:20:33.138] thr=4   paramiko.transport: Secsh channel 0 opened.
DEB [20190430-21:20:33.224] thr=4   paramiko.transport: [chan 0] Sesch channel 0 request ok
DEB [20190430-21:20:33.310] thr=4   paramiko.transport: [chan 0] Sesch channel 0 request ok
DEB [20190430-21:22:27.780] thr=1   paramiko.transport: [chan 0] EOF received (0)
DEB [20190430-21:22:27.780] thr=1   paramiko.transport: [chan 0] EOF sent (0)

我从日志中收集的内容-看起来paramiko.transport已经在使用线程。另外,如果我正确阅读此内容(?),则会打开1个频道。 它被列为两次打开(对于每个脚本-在21:20:27一次,然后在21:20:33一次)。但是,这两个都称为chan 0,因此可能会重用相同的chanel? 从第一个作业发出ssh.close()后,将关闭一个通道。这种方式很有意义,因为它已经与同一服务器建立了活动连接,因此最好重用它。 我也尝试过使用频道,但是得到了相同的结果。

由此看来,我可能需要找出一种启动/强制多个渠道的方法-很难以交互方式进行,并且通过某种类型的循环,它可能会为每个呼叫打开未使用的渠道,并且需要逻辑来将下一个呼叫发送到另一个频道。

我想确保正在使用的应用程序完全能够同时处理多个任务,因此我想提出这个问题,看看是否有人更熟悉paramiko可以为我提供一些帮助指针-非常感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

原来我的问题是在test_ssh和run_ssh函数之间共享connect.py中的“ ssh”对象。我教过我已经测试过了,但是显然没有。

为确保没有混乱,我也对它们进行了不同的命名,并将它们放在如下函数中:

def test_ssh():
  sshtest = paramiko.SSHClient()
  sshtest.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  try:
      sshtest.connect(HOST, username=USER, key_filename=KEY)
  except Exception as e:
      raise ValueError("Connection Error: {}".format(str(e)))
      sshtest.close()
  finally:
      sshtest.close()

def run_ssh(cmd, logname):
  ssh = paramiko.SSHClient()
  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  ssh.connect(HOST, username=USER, key_filename=KEY)
  file = open(logname, 'w')
  stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
  for line in iter(stdout.readline, ''):
    file.write(''.join(line))
  file.close()
  ssh.close()

使用此代码,一切都能按预期进行。