From 5e1cf6fdd96a29aca0dd217f9787d39588ccfbc7 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Wed, 5 Aug 2020 17:34:59 +0800 Subject: [PATCH 1/3] Use paramiko for ssh connection in test_wr_arp The test_wr_arp script put ssh key of ptfhost on dut to have password less ssh connection. Then the ptfhost can execute command remote on dut through this ssh connection. The problem is that the ssh key saved in ~/.ssh/authorized_keys on dut host may not persist after warm reboot. It would also be a problem when secure boot feature is introduced. This PR updated the test_wr_arp script to use paramiko for ssh connection and remote command execution between ptfhost and dut. Changes: * Update the ptf test script to use paramiko for SSH connection with dut * Disabled loganalyzer because test_wr_arp need to do warm-reboot on dut Signed-off-by: Xin Wang --- ansible/roles/test/files/ptftests/wr_arp.py | 44 +++++++++++++++------ tests/arp/test_wr_arp.py | 23 +++-------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/ansible/roles/test/files/ptftests/wr_arp.py b/ansible/roles/test/files/ptftests/wr_arp.py index 39240138985..5d3a72a468d 100644 --- a/ansible/roles/test/files/ptftests/wr_arp.py +++ b/ansible/roles/test/files/ptftests/wr_arp.py @@ -12,6 +12,7 @@ import datetime import traceback import sys +import socket import threading from collections import defaultdict from pprint import pprint @@ -22,6 +23,8 @@ from ptf import config import ptf.dataplane as dataplane import ptf.testutils as testutils +import paramiko +from paramiko.ssh_exception import BadHostKeyException, AuthenticationException, SSHException class ArpTest(BaseTest): @@ -60,19 +63,34 @@ def cmd(self, cmds): return stdout, stderr, return_code - def ssh(self, cmds): - ssh_cmds = ["ssh", "-oStrictHostKeyChecking=no", "-oServerAliveInterval=2", "admin@" + self.dut_ssh] - ssh_cmds.extend(cmds) - stdout, stderr, return_code = self.cmd(ssh_cmds) - if stdout != []: - self.log("stdout from dut: '%s'" % str(stdout)) - if stderr != []: - self.log("stderr from dut '%s'" % str(stderr)) - self.log("return code from dut: '%s'" % str(return_code)) + def dut_exec_cmd(self, cmd): + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + stdout = stderr = [] + return_code = 1 + + try: + client.connect(self.dut_ssh, username=self.dut_username, password=self.dut_password, allow_agent=False) + si, so, se = client.exec_command(cmd, timeout=30) + stdout = so.readlines() + stderr = se.readlines() + self.log('executed command {}, stdout={}, stderr={}'.format(cmd, stdout, stderr)) + return_code = 0 + except socket.timeout as e: + self.log('Caught exception socket.timeout: {}, {}, {}'.format(repr(e), str(e), type(e))) + return_code = 255 + except Exception as e: + self.log('Exception caught: {}, {}, type: {}'.format(repr(e), str(e), type(e))) + self.log(sys.exc_info()) + finally: + client.close() + + self.log("return_code={}, stdout={}, stderr={}".format(return_code, stdout, stderr)) if return_code == 0: return True, str(stdout) - elif return_code == 255 and 'Timeout, server' in stderr and 'not responding' in stderr: + elif return_code == 255: return True, str(stdout) else: return False, "return code: %d. stdout = '%s' stderr = '%s'" % (return_code, str(stdout), str(stderr)) @@ -82,14 +100,14 @@ def dut_thr(self, q_from, q_to): cmd = q_from.get() if cmd == 'WR': self.log("Rebooting remote side") - res, res_text = self.ssh(["sudo", "warm-reboot", "-c", self.ferret_ip]) + res, res_text = self.dut_exec_cmd("sudo warm-reboot -c {}".format(self.ferret_ip)) if res: q_to.put('ok: %s' % res_text) else: q_to.put('error: %s' % res_text) elif cmd == 'uptime': self.log("Check uptime remote side") - res, res_text = self.ssh(["uptime", "-s"]) + res, res_text = self.dut_exec_cmd("uptime -s") if res: q_to.put('ok: %s' % res_text) else: @@ -193,6 +211,8 @@ def setUp(self): config = self.get_param('config_file') self.ferret_ip = self.get_param('ferret_ip') self.dut_ssh = self.get_param('dut_ssh') + self.dut_username = self.get_param('dut_username') + self.dut_password = self.get_param('dut_password') self.how_long = int(self.get_param('how_long', required=False, default=300)) if not os.path.isfile(config): diff --git a/tests/arp/test_wr_arp.py b/tests/arp/test_wr_arp.py index 99472847882..6af49d18eb1 100644 --- a/tests/arp/test_wr_arp.py +++ b/tests/arp/test_wr_arp.py @@ -6,13 +6,13 @@ from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # lgtm[py/unused-import] from tests.common.fixtures.ptfhost_utils import change_mac_addresses # lgtm[py/unused-import] from tests.common.fixtures.ptfhost_utils import remove_ip_addresses # lgtm[py/unused-import] -from tests.common.platform.ssh_utils import prepare_testbed_ssh_keys as prepareTestbedSshKeys from tests.ptf_runner import ptf_runner logger = logging.getLogger(__name__) pytestmark = [ - pytest.mark.topology('t0') + pytest.mark.topology('t0'), + pytest.mark.disable_loganalyzer ] # Globals @@ -159,22 +159,7 @@ def setupRouteToPtfhost(self, duthost, ptfhost): assert result["rc"] == 0 or "No such process" in result["stderr"], \ "Failed to delete route with error '{0}'".format(result["stderr"]) - @pytest.fixture(scope='class', autouse=True) - def prepareSshKeys(self, duthost, ptfhost, creds): - ''' - Prepares testbed ssh keys by generating ssh key on ptf host and adding this key to known_hosts on duthost - This class-scope fixture runs once before test start - - Args: - duthost (AnsibleHost): Device Under Test (DUT) - ptfhost (AnsibleHost): Packet Test Framework (PTF) - - Returns: - None - ''' - prepareTestbedSshKeys(duthost, ptfhost, creds['sonicadmin_user']) - - def testWrArp(self, request, duthost, ptfhost): + def testWrArp(self, request, duthost, ptfhost, creds): ''' Control Plane Assistant test for Warm-Reboot. @@ -206,6 +191,8 @@ def testWrArp(self, request, duthost, ptfhost): params={ 'ferret_ip' : ptfIp, 'dut_ssh' : dutIp, + 'dut_username': creds['sonicadmin_user'], + 'dut_password': creds['sonicadmin_password'], 'config_file' : VXLAN_CONFIG_FILE, 'how_long' : testDuration, }, From 9db07ea8940afa14652e9842a24ace20cb5ece92 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 6 Aug 2020 09:55:30 +0800 Subject: [PATCH 2/3] Reuse the existing library --- .../test/files/ptftests/device_connection.py | 8 ++++++ ansible/roles/test/files/ptftests/wr_arp.py | 26 +++---------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/ansible/roles/test/files/ptftests/device_connection.py b/ansible/roles/test/files/ptftests/device_connection.py index a29ea493b06..5a475eba671 100644 --- a/ansible/roles/test/files/ptftests/device_connection.py +++ b/ansible/roles/test/files/ptftests/device_connection.py @@ -1,5 +1,6 @@ import paramiko import logging +import socket from paramiko.ssh_exception import BadHostKeyException, AuthenticationException, SSHException logger = logging.getLogger(__name__) @@ -57,6 +58,13 @@ def execCommand(self, cmd, timeout=DEFAULT_CMD_EXECUTION_TIMEOUT_SEC): logger.error('SSH Authentiaction failure with message: %s' % authenticationException) except BadHostKeyException as badHostKeyException: logger.error('SSH Authentiaction failure with message: %s' % badHostKeyException) + except socket.timeout as e: + # The ssh session will timeout in case of a successful reboot + logger.error('Caught exception socket.timeout: {}, {}, {}'.format(repr(e), str(e), type(e))) + retValue = 255 + except Exception as e: + logger.error('Exception caught: {}, {}, type: {}'.format(repr(e), str(e), type(e))) + logger.error(sys.exc_info()) finally: client.close() diff --git a/ansible/roles/test/files/ptftests/wr_arp.py b/ansible/roles/test/files/ptftests/wr_arp.py index 5d3a72a468d..cdb781f7e06 100644 --- a/ansible/roles/test/files/ptftests/wr_arp.py +++ b/ansible/roles/test/files/ptftests/wr_arp.py @@ -25,6 +25,7 @@ import ptf.testutils as testutils import paramiko from paramiko.ssh_exception import BadHostKeyException, AuthenticationException, SSHException +from device_connection import DeviceConnection class ArpTest(BaseTest): @@ -64,28 +65,8 @@ def cmd(self, cmds): return stdout, stderr, return_code def dut_exec_cmd(self, cmd): - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - stdout = stderr = [] - return_code = 1 - - try: - client.connect(self.dut_ssh, username=self.dut_username, password=self.dut_password, allow_agent=False) - si, so, se = client.exec_command(cmd, timeout=30) - stdout = so.readlines() - stderr = se.readlines() - self.log('executed command {}, stdout={}, stderr={}'.format(cmd, stdout, stderr)) - return_code = 0 - except socket.timeout as e: - self.log('Caught exception socket.timeout: {}, {}, {}'.format(repr(e), str(e), type(e))) - return_code = 255 - except Exception as e: - self.log('Exception caught: {}, {}, type: {}'.format(repr(e), str(e), type(e))) - self.log(sys.exc_info()) - finally: - client.close() - + self.log("Executing cmd='{}'".format(cmd)) + stdout, stderr, return_code = self.dut_connection.execCommand(cmd, timeout=30) self.log("return_code={}, stdout={}, stderr={}".format(return_code, stdout, stderr)) if return_code == 0: @@ -213,6 +194,7 @@ def setUp(self): self.dut_ssh = self.get_param('dut_ssh') self.dut_username = self.get_param('dut_username') self.dut_password = self.get_param('dut_password') + self.dut_connection = DeviceConnection(self.dut_ssh, username=self.dut_username, password=self.dut_password) self.how_long = int(self.get_param('how_long', required=False, default=300)) if not os.path.isfile(config): From ab5df0ddee6ae2e8f88d013dd78df18ad18d4bf4 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 6 Aug 2020 12:45:21 +0800 Subject: [PATCH 3/3] Remove unused imports --- ansible/roles/test/files/ptftests/wr_arp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ansible/roles/test/files/ptftests/wr_arp.py b/ansible/roles/test/files/ptftests/wr_arp.py index cdb781f7e06..c05f89715f1 100644 --- a/ansible/roles/test/files/ptftests/wr_arp.py +++ b/ansible/roles/test/files/ptftests/wr_arp.py @@ -23,8 +23,6 @@ from ptf import config import ptf.dataplane as dataplane import ptf.testutils as testutils -import paramiko -from paramiko.ssh_exception import BadHostKeyException, AuthenticationException, SSHException from device_connection import DeviceConnection