|
@@ -0,0 +1,266 @@
|
|
|
+# Copyright (C) 2016 Intel Corporation
|
|
|
+# Released under the MIT license (see COPYING.MIT)
|
|
|
+
|
|
|
+import os
|
|
|
+import time
|
|
|
+import select
|
|
|
+import logging
|
|
|
+import subprocess
|
|
|
+
|
|
|
+from . import OETarget
|
|
|
+
|
|
|
+class OESSHTarget(OETarget):
|
|
|
+ def __init__(self, logger, ip, server_ip, timeout=300, user='root',
|
|
|
+ port=None, **kwargs):
|
|
|
+ if not logger:
|
|
|
+ logger = logging.getLogger('target')
|
|
|
+ logger.setLevel(logging.INFO)
|
|
|
+ filePath = os.path.join(os.getcwd(), 'remoteTarget.log')
|
|
|
+ fileHandler = logging.FileHandler(filePath, 'w', 'utf-8')
|
|
|
+ formatter = logging.Formatter(
|
|
|
+ '%(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
|
|
|
+ '%H:%M:%S')
|
|
|
+ fileHandler.setFormatter(formatter)
|
|
|
+ logger.addHandler(fileHandler)
|
|
|
+
|
|
|
+ super(OESSHTarget, self).__init__(logger)
|
|
|
+ self.ip = ip
|
|
|
+ self.server_ip = server_ip
|
|
|
+ self.timeout = timeout
|
|
|
+ self.user = user
|
|
|
+ ssh_options = [
|
|
|
+ '-o', 'UserKnownHostsFile=/dev/null',
|
|
|
+ '-o', 'StrictHostKeyChecking=no',
|
|
|
+ '-o', 'LogLevel=ERROR'
|
|
|
+ ]
|
|
|
+ self.ssh = ['ssh', '-l', self.user ] + ssh_options
|
|
|
+ self.scp = ['scp'] + ssh_options
|
|
|
+ if port:
|
|
|
+ self.ssh = self.ssh + [ '-p', port ]
|
|
|
+ self.scp = self.scp + [ '-P', port ]
|
|
|
+
|
|
|
+ def start(self, **kwargs):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def stop(self, **kwargs):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def _run(self, command, timeout=None, ignore_status=True):
|
|
|
+ """
|
|
|
+ Runs command in target using SSHProcess.
|
|
|
+ """
|
|
|
+ self.logger.debug("[Running]$ %s" % " ".join(command))
|
|
|
+
|
|
|
+ starttime = time.time()
|
|
|
+ status, output = SSHCall(command, self.logger, timeout)
|
|
|
+ self.logger.debug("[Command returned '%d' after %.2f seconds]"
|
|
|
+ "" % (status, time.time() - starttime))
|
|
|
+
|
|
|
+ if status and not ignore_status:
|
|
|
+ raise AssertionError("Command '%s' returned non-zero exit "
|
|
|
+ "status %d:\n%s" % (command, status, output))
|
|
|
+
|
|
|
+ return (status, output)
|
|
|
+
|
|
|
+ def run(self, command, timeout=None):
|
|
|
+ """
|
|
|
+ Runs command in target.
|
|
|
+
|
|
|
+ command: Command to run on target.
|
|
|
+ timeout: <value>: Kill command after <val> seconds.
|
|
|
+ None: Kill command default value seconds.
|
|
|
+ 0: No timeout, runs until return.
|
|
|
+ """
|
|
|
+ targetCmd = 'export PATH=/usr/sbin:/sbin:/usr/bin:/bin; %s' % command
|
|
|
+ sshCmd = self.ssh + [self.ip, targetCmd]
|
|
|
+
|
|
|
+ if timeout:
|
|
|
+ processTimeout = timeout
|
|
|
+ elif timeout==0:
|
|
|
+ processTimeout = None
|
|
|
+ else:
|
|
|
+ processTimeout = self.timeout
|
|
|
+
|
|
|
+ status, output = self._run(sshCmd, processTimeout, True)
|
|
|
+ self.logger.info('\nCommand: %s\nOutput: %s\n' % (command, output))
|
|
|
+ return (status, output)
|
|
|
+
|
|
|
+ def copyTo(self, localSrc, remoteDst):
|
|
|
+ """
|
|
|
+ Copy file to target.
|
|
|
+
|
|
|
+ If local file is symlink, recreate symlink in target.
|
|
|
+ """
|
|
|
+ if os.path.islink(localSrc):
|
|
|
+ link = os.readlink(localSrc)
|
|
|
+ dstDir, dstBase = os.path.split(remoteDst)
|
|
|
+ sshCmd = 'cd %s; ln -s %s %s' % (dstDir, link, dstBase)
|
|
|
+ return self.run(sshCmd)
|
|
|
+
|
|
|
+ else:
|
|
|
+ remotePath = '%s@%s:%s' % (self.user, self.ip, remoteDst)
|
|
|
+ scpCmd = self.scp + [localSrc, remotePath]
|
|
|
+ return self._run(scpCmd, ignore_status=False)
|
|
|
+
|
|
|
+ def copyFrom(self, remoteSrc, localDst):
|
|
|
+ """
|
|
|
+ Copy file from target.
|
|
|
+ """
|
|
|
+ remotePath = '%s@%s:%s' % (self.user, self.ip, remoteSrc)
|
|
|
+ scpCmd = self.scp + [remotePath, localDst]
|
|
|
+ return self._run(scpCmd, ignore_status=False)
|
|
|
+
|
|
|
+ def copyDirTo(self, localSrc, remoteDst):
|
|
|
+ """
|
|
|
+ Copy recursively localSrc directory to remoteDst in target.
|
|
|
+ """
|
|
|
+
|
|
|
+ for root, dirs, files in os.walk(localSrc):
|
|
|
+ # Create directories in the target as needed
|
|
|
+ for d in dirs:
|
|
|
+ tmpDir = os.path.join(root, d).replace(localSrc, "")
|
|
|
+ newDir = os.path.join(remoteDst, tmpDir.lstrip("/"))
|
|
|
+ cmd = "mkdir -p %s" % newDir
|
|
|
+ self.run(cmd)
|
|
|
+
|
|
|
+ # Copy files into the target
|
|
|
+ for f in files:
|
|
|
+ tmpFile = os.path.join(root, f).replace(localSrc, "")
|
|
|
+ dstFile = os.path.join(remoteDst, tmpFile.lstrip("/"))
|
|
|
+ srcFile = os.path.join(root, f)
|
|
|
+ self.copyTo(srcFile, dstFile)
|
|
|
+
|
|
|
+ def deleteFiles(self, remotePath, files):
|
|
|
+ """
|
|
|
+ Deletes files in target's remotePath.
|
|
|
+ """
|
|
|
+
|
|
|
+ cmd = "rm"
|
|
|
+ if not isinstance(files, list):
|
|
|
+ files = [files]
|
|
|
+
|
|
|
+ for f in files:
|
|
|
+ cmd = "%s %s" % (cmd, os.path.join(remotePath, f))
|
|
|
+
|
|
|
+ self.run(cmd)
|
|
|
+
|
|
|
+
|
|
|
+ def deleteDir(self, remotePath):
|
|
|
+ """
|
|
|
+ Deletes target's remotePath directory.
|
|
|
+ """
|
|
|
+
|
|
|
+ cmd = "rmdir %s" % remotePath
|
|
|
+ self.run(cmd)
|
|
|
+
|
|
|
+
|
|
|
+ def deleteDirStructure(self, localPath, remotePath):
|
|
|
+ """
|
|
|
+ Delete recursively localPath structure directory in target's remotePath.
|
|
|
+
|
|
|
+ This function is very usefult to delete a package that is installed in
|
|
|
+ the DUT and the host running the test has such package extracted in tmp
|
|
|
+ directory.
|
|
|
+
|
|
|
+ Example:
|
|
|
+ pwd: /home/user/tmp
|
|
|
+ tree: .
|
|
|
+ └── work
|
|
|
+ ├── dir1
|
|
|
+ │ └── file1
|
|
|
+ └── dir2
|
|
|
+
|
|
|
+ localpath = "/home/user/tmp" and remotepath = "/home/user"
|
|
|
+
|
|
|
+ With the above variables this function will try to delete the
|
|
|
+ directory in the DUT in this order:
|
|
|
+ /home/user/work/dir1/file1
|
|
|
+ /home/user/work/dir1 (if dir is empty)
|
|
|
+ /home/user/work/dir2 (if dir is empty)
|
|
|
+ /home/user/work (if dir is empty)
|
|
|
+ """
|
|
|
+
|
|
|
+ for root, dirs, files in os.walk(localPath, topdown=False):
|
|
|
+ # Delete files first
|
|
|
+ tmpDir = os.path.join(root).replace(localPath, "")
|
|
|
+ remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
|
|
|
+ self.deleteFiles(remoteDir, files)
|
|
|
+
|
|
|
+ # Remove dirs if empty
|
|
|
+ for d in dirs:
|
|
|
+ tmpDir = os.path.join(root, d).replace(localPath, "")
|
|
|
+ remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
|
|
|
+ self.deleteDir(remoteDir)
|
|
|
+
|
|
|
+def SSHCall(command, logger, timeout=None, **opts):
|
|
|
+
|
|
|
+ def run():
|
|
|
+ nonlocal output
|
|
|
+ nonlocal process
|
|
|
+ starttime = time.time()
|
|
|
+ process = subprocess.Popen(command, **options)
|
|
|
+ if timeout:
|
|
|
+ endtime = starttime + timeout
|
|
|
+ eof = False
|
|
|
+ while time.time() < endtime and not eof:
|
|
|
+ logger.debug('time: %s, endtime: %s' % (time.time(), endtime))
|
|
|
+ try:
|
|
|
+ if select.select([process.stdout], [], [], 5)[0] != []:
|
|
|
+ data = os.read(process.stdout.fileno(), 1024)
|
|
|
+ if not data:
|
|
|
+ process.stdout.close()
|
|
|
+ eof = True
|
|
|
+ else:
|
|
|
+ data = data.decode("utf-8")
|
|
|
+ output += data
|
|
|
+ logger.debug('Partial data from SSH call: %s' % data)
|
|
|
+ endtime = time.time() + timeout
|
|
|
+ except InterruptedError:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # process hasn't returned yet
|
|
|
+ if not eof:
|
|
|
+ process.terminate()
|
|
|
+ time.sleep(5)
|
|
|
+ try:
|
|
|
+ process.kill()
|
|
|
+ except OSError:
|
|
|
+ pass
|
|
|
+ endtime = time.time() - starttime
|
|
|
+ lastline = ("\nProcess killed - no output for %d seconds. Total"
|
|
|
+ " running time: %d seconds." % (timeout, endtime))
|
|
|
+ logger.debug('Received data from SSH call %s ' % lastline)
|
|
|
+ output += lastline
|
|
|
+
|
|
|
+ else:
|
|
|
+ output = process.communicate()[0].decode("utf-8")
|
|
|
+ logger.debug('Data from SSH call: %s' % output.rstrip())
|
|
|
+
|
|
|
+ options = {
|
|
|
+ "stdout": subprocess.PIPE,
|
|
|
+ "stderr": subprocess.STDOUT,
|
|
|
+ "stdin": None,
|
|
|
+ "shell": False,
|
|
|
+ "bufsize": -1,
|
|
|
+ "preexec_fn": os.setsid,
|
|
|
+ }
|
|
|
+ options.update(opts)
|
|
|
+ output = ''
|
|
|
+ process = None
|
|
|
+
|
|
|
+ # Unset DISPLAY which means we won't trigger SSH_ASKPASS
|
|
|
+ env = os.environ.copy()
|
|
|
+ if "DISPLAY" in env:
|
|
|
+ del env['DISPLAY']
|
|
|
+ options['env'] = env
|
|
|
+
|
|
|
+ try:
|
|
|
+ run()
|
|
|
+ except:
|
|
|
+ # Need to guard against a SystemExit or other exception ocurring
|
|
|
+ # whilst running and ensure we don't leave a process behind.
|
|
|
+ if process.poll() is None:
|
|
|
+ process.kill()
|
|
|
+ logger.debug('Something went wrong, killing SSH process')
|
|
|
+ raise
|
|
|
+ return (process.wait(), output.rstrip())
|