ssh.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # Copyright (C) 2016 Intel Corporation
  2. # Released under the MIT license (see COPYING.MIT)
  3. import os
  4. import time
  5. import select
  6. import logging
  7. import subprocess
  8. from . import OETarget
  9. class OESSHTarget(OETarget):
  10. def __init__(self, logger, ip, server_ip, timeout=300, user='root',
  11. port=None, **kwargs):
  12. if not logger:
  13. logger = logging.getLogger('target')
  14. logger.setLevel(logging.INFO)
  15. filePath = os.path.join(os.getcwd(), 'remoteTarget.log')
  16. fileHandler = logging.FileHandler(filePath, 'w', 'utf-8')
  17. formatter = logging.Formatter(
  18. '%(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
  19. '%H:%M:%S')
  20. fileHandler.setFormatter(formatter)
  21. logger.addHandler(fileHandler)
  22. super(OESSHTarget, self).__init__(logger)
  23. self.ip = ip
  24. self.server_ip = server_ip
  25. self.timeout = timeout
  26. self.user = user
  27. ssh_options = [
  28. '-o', 'UserKnownHostsFile=/dev/null',
  29. '-o', 'StrictHostKeyChecking=no',
  30. '-o', 'LogLevel=ERROR'
  31. ]
  32. self.ssh = ['ssh', '-l', self.user ] + ssh_options
  33. self.scp = ['scp'] + ssh_options
  34. if port:
  35. self.ssh = self.ssh + [ '-p', port ]
  36. self.scp = self.scp + [ '-P', port ]
  37. def start(self, **kwargs):
  38. pass
  39. def stop(self, **kwargs):
  40. pass
  41. def _run(self, command, timeout=None, ignore_status=True):
  42. """
  43. Runs command in target using SSHProcess.
  44. """
  45. self.logger.debug("[Running]$ %s" % " ".join(command))
  46. starttime = time.time()
  47. status, output = SSHCall(command, self.logger, timeout)
  48. self.logger.debug("[Command returned '%d' after %.2f seconds]"
  49. "" % (status, time.time() - starttime))
  50. if status and not ignore_status:
  51. raise AssertionError("Command '%s' returned non-zero exit "
  52. "status %d:\n%s" % (command, status, output))
  53. return (status, output)
  54. def run(self, command, timeout=None):
  55. """
  56. Runs command in target.
  57. command: Command to run on target.
  58. timeout: <value>: Kill command after <val> seconds.
  59. None: Kill command default value seconds.
  60. 0: No timeout, runs until return.
  61. """
  62. targetCmd = 'export PATH=/usr/sbin:/sbin:/usr/bin:/bin; %s' % command
  63. sshCmd = self.ssh + [self.ip, targetCmd]
  64. if timeout:
  65. processTimeout = timeout
  66. elif timeout==0:
  67. processTimeout = None
  68. else:
  69. processTimeout = self.timeout
  70. status, output = self._run(sshCmd, processTimeout, True)
  71. self.logger.info('\nCommand: %s\nOutput: %s\n' % (command, output))
  72. return (status, output)
  73. def copyTo(self, localSrc, remoteDst):
  74. """
  75. Copy file to target.
  76. If local file is symlink, recreate symlink in target.
  77. """
  78. if os.path.islink(localSrc):
  79. link = os.readlink(localSrc)
  80. dstDir, dstBase = os.path.split(remoteDst)
  81. sshCmd = 'cd %s; ln -s %s %s' % (dstDir, link, dstBase)
  82. return self.run(sshCmd)
  83. else:
  84. remotePath = '%s@%s:%s' % (self.user, self.ip, remoteDst)
  85. scpCmd = self.scp + [localSrc, remotePath]
  86. return self._run(scpCmd, ignore_status=False)
  87. def copyFrom(self, remoteSrc, localDst):
  88. """
  89. Copy file from target.
  90. """
  91. remotePath = '%s@%s:%s' % (self.user, self.ip, remoteSrc)
  92. scpCmd = self.scp + [remotePath, localDst]
  93. return self._run(scpCmd, ignore_status=False)
  94. def copyDirTo(self, localSrc, remoteDst):
  95. """
  96. Copy recursively localSrc directory to remoteDst in target.
  97. """
  98. for root, dirs, files in os.walk(localSrc):
  99. # Create directories in the target as needed
  100. for d in dirs:
  101. tmpDir = os.path.join(root, d).replace(localSrc, "")
  102. newDir = os.path.join(remoteDst, tmpDir.lstrip("/"))
  103. cmd = "mkdir -p %s" % newDir
  104. self.run(cmd)
  105. # Copy files into the target
  106. for f in files:
  107. tmpFile = os.path.join(root, f).replace(localSrc, "")
  108. dstFile = os.path.join(remoteDst, tmpFile.lstrip("/"))
  109. srcFile = os.path.join(root, f)
  110. self.copyTo(srcFile, dstFile)
  111. def deleteFiles(self, remotePath, files):
  112. """
  113. Deletes files in target's remotePath.
  114. """
  115. cmd = "rm"
  116. if not isinstance(files, list):
  117. files = [files]
  118. for f in files:
  119. cmd = "%s %s" % (cmd, os.path.join(remotePath, f))
  120. self.run(cmd)
  121. def deleteDir(self, remotePath):
  122. """
  123. Deletes target's remotePath directory.
  124. """
  125. cmd = "rmdir %s" % remotePath
  126. self.run(cmd)
  127. def deleteDirStructure(self, localPath, remotePath):
  128. """
  129. Delete recursively localPath structure directory in target's remotePath.
  130. This function is very usefult to delete a package that is installed in
  131. the DUT and the host running the test has such package extracted in tmp
  132. directory.
  133. Example:
  134. pwd: /home/user/tmp
  135. tree: .
  136. └── work
  137. ├── dir1
  138. │   └── file1
  139. └── dir2
  140. localpath = "/home/user/tmp" and remotepath = "/home/user"
  141. With the above variables this function will try to delete the
  142. directory in the DUT in this order:
  143. /home/user/work/dir1/file1
  144. /home/user/work/dir1 (if dir is empty)
  145. /home/user/work/dir2 (if dir is empty)
  146. /home/user/work (if dir is empty)
  147. """
  148. for root, dirs, files in os.walk(localPath, topdown=False):
  149. # Delete files first
  150. tmpDir = os.path.join(root).replace(localPath, "")
  151. remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
  152. self.deleteFiles(remoteDir, files)
  153. # Remove dirs if empty
  154. for d in dirs:
  155. tmpDir = os.path.join(root, d).replace(localPath, "")
  156. remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
  157. self.deleteDir(remoteDir)
  158. def SSHCall(command, logger, timeout=None, **opts):
  159. def run():
  160. nonlocal output
  161. nonlocal process
  162. starttime = time.time()
  163. process = subprocess.Popen(command, **options)
  164. if timeout:
  165. endtime = starttime + timeout
  166. eof = False
  167. while time.time() < endtime and not eof:
  168. logger.debug('time: %s, endtime: %s' % (time.time(), endtime))
  169. try:
  170. if select.select([process.stdout], [], [], 5)[0] != []:
  171. data = os.read(process.stdout.fileno(), 1024)
  172. if not data:
  173. process.stdout.close()
  174. eof = True
  175. else:
  176. data = data.decode("utf-8", errors='replace')
  177. output += data
  178. logger.debug('Partial data from SSH call: %s' % data)
  179. endtime = time.time() + timeout
  180. except InterruptedError:
  181. continue
  182. # process hasn't returned yet
  183. if not eof:
  184. process.terminate()
  185. time.sleep(5)
  186. try:
  187. process.kill()
  188. except OSError:
  189. pass
  190. endtime = time.time() - starttime
  191. lastline = ("\nProcess killed - no output for %d seconds. Total"
  192. " running time: %d seconds." % (timeout, endtime))
  193. logger.debug('Received data from SSH call %s ' % lastline)
  194. output += lastline
  195. else:
  196. output = process.communicate()[0].decode("utf-8", errors='replace')
  197. logger.debug('Data from SSH call: %s' % output.rstrip())
  198. options = {
  199. "stdout": subprocess.PIPE,
  200. "stderr": subprocess.STDOUT,
  201. "stdin": None,
  202. "shell": False,
  203. "bufsize": -1,
  204. "preexec_fn": os.setsid,
  205. }
  206. options.update(opts)
  207. output = ''
  208. process = None
  209. # Unset DISPLAY which means we won't trigger SSH_ASKPASS
  210. env = os.environ.copy()
  211. if "DISPLAY" in env:
  212. del env['DISPLAY']
  213. options['env'] = env
  214. try:
  215. run()
  216. except:
  217. # Need to guard against a SystemExit or other exception ocurring
  218. # whilst running and ensure we don't leave a process behind.
  219. if process.poll() is None:
  220. process.kill()
  221. logger.debug('Something went wrong, killing SSH process')
  222. raise
  223. return (process.wait(), output.rstrip())