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