1# Host class 2# Copyright (c) 2016, Qualcomm Atheros, Inc. 3# 4# This software may be distributed under the terms of the BSD license. 5# See README for more details. 6 7import logging 8import subprocess 9import threading 10import tempfile 11import os 12import traceback 13import select 14 15logger = logging.getLogger() 16 17def remote_compatible(func): 18 func.remote_compatible = True 19 return func 20 21def execute_thread(command, reply): 22 cmd = ' '.join(command) 23 logger.debug("thread run: " + cmd) 24 err = tempfile.TemporaryFile() 25 try: 26 status = 0 27 buf = subprocess.check_output(command, stderr=err).decode() 28 except subprocess.CalledProcessError as e: 29 status = e.returncode 30 err.seek(0) 31 buf = err.read() 32 err.close() 33 34 logger.debug("thread cmd: " + cmd) 35 logger.debug("thread exit status: " + str(status)) 36 logger.debug("thread exit buf: " + str(buf)) 37 reply.append(status) 38 reply.append(buf) 39 40def gen_reaper_file(conf): 41 fd, filename = tempfile.mkstemp(dir='/tmp', prefix=conf + '-') 42 f = os.fdopen(fd, 'w') 43 44 f.write("#!/bin/sh\n") 45 f.write("name=\"$(basename $0)\"\n") 46 f.write("echo $$ > /tmp/$name.pid\n") 47 f.write("exec \"$@\"\n"); 48 49 return filename; 50 51class Host(): 52 def __init__(self, host=None, ifname=None, port=None, name="", user="root"): 53 self.host = host 54 self.name = name 55 self.user = user 56 self.monitors = [] 57 self.monitor_thread = None 58 self.logs = [] 59 self.ifname = ifname 60 self.port = port 61 self.dev = None 62 self.monitor_params = [] 63 if self.name == "" and host != None: 64 self.name = host 65 66 def local_execute(self, command): 67 logger.debug("execute: " + str(command)) 68 err = tempfile.TemporaryFile() 69 try: 70 status = 0 71 buf = subprocess.check_output(command, stderr=err) 72 except subprocess.CalledProcessError as e: 73 status = e.returncode 74 err.seek(0) 75 buf = err.read() 76 err.close() 77 78 logger.debug("status: " + str(status)) 79 logger.debug("buf: " + str(buf)) 80 return status, buf.decode() 81 82 def execute(self, command): 83 if self.host is None: 84 return self.local_execute(command) 85 86 cmd = ["ssh", self.user + "@" + self.host, ' '.join(command)] 87 _cmd = self.name + " execute: " + ' '.join(cmd) 88 logger.debug(_cmd) 89 err = tempfile.TemporaryFile() 90 try: 91 status = 0 92 buf = subprocess.check_output(cmd, stderr=err) 93 except subprocess.CalledProcessError as e: 94 status = e.returncode 95 err.seek(0) 96 buf = err.read() 97 err.close() 98 99 logger.debug(self.name + " status: " + str(status)) 100 logger.debug(self.name + " buf: " + str(buf)) 101 return status, buf.decode() 102 103 # async execute 104 def thread_run(self, command, res, use_reaper=True): 105 if use_reaper: 106 filename = gen_reaper_file("reaper") 107 self.send_file(filename, filename) 108 self.execute(["chmod", "755", filename]) 109 _command = [filename] + command 110 else: 111 filename = "" 112 _command = command 113 114 if self.host is None: 115 cmd = _command 116 else: 117 cmd = ["ssh", self.user + "@" + self.host, ' '.join(_command)] 118 _cmd = self.name + " thread_run: " + ' '.join(cmd) 119 logger.debug(_cmd) 120 t = threading.Thread(target=execute_thread, name=filename, args=(cmd, res)) 121 t.start() 122 return t 123 124 def thread_stop(self, t): 125 if t.name.find("reaper") == -1: 126 raise Exception("use_reaper required") 127 128 pid_file = t.name + ".pid" 129 130 if t.is_alive(): 131 cmd = ["kill `cat " + pid_file + "`"] 132 self.execute(cmd) 133 134 # try again 135 self.thread_wait(t, 5) 136 if t.is_alive(): 137 cmd = ["kill `cat " + pid_file + "`"] 138 self.execute(cmd) 139 140 # try with -9 141 self.thread_wait(t, 5) 142 if t.is_alive(): 143 cmd = ["kill -9 `cat " + pid_file + "`"] 144 self.execute(cmd) 145 146 self.thread_wait(t, 5) 147 if t.is_alive(): 148 raise Exception("thread still alive") 149 150 self.execute(["rm", pid_file]) 151 self.execute(["rm", t.name]) 152 self.local_execute(["rm", t.name]) 153 154 def thread_wait(self, t, wait=None): 155 if wait == None: 156 wait_str = "infinite" 157 else: 158 wait_str = str(wait) + "s" 159 160 logger.debug(self.name + " thread_wait(" + wait_str + "): ") 161 if t.is_alive(): 162 t.join(wait) 163 164 def pending(self, s, timeout=0): 165 [r, w, e] = select.select([s], [], [], timeout) 166 if r: 167 return True 168 return False 169 170 def proc_run(self, command): 171 filename = gen_reaper_file("reaper") 172 self.send_file(filename, filename) 173 self.execute(["chmod", "755", filename]) 174 _command = [filename] + command 175 176 if self.host: 177 cmd = ["ssh", self.user + "@" + self.host, ' '.join(_command)] 178 else: 179 cmd = _command 180 181 _cmd = self.name + " proc_run: " + ' '.join(cmd) 182 logger.debug(_cmd) 183 err = tempfile.TemporaryFile() 184 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=err) 185 proc.reaper_file = filename 186 return proc 187 188 def proc_wait_event(self, proc, events, timeout=10): 189 if not isinstance(events, list): 190 raise Exception("proc_wait_event() events not a list") 191 192 logger.debug(self.name + " proc_wait_event: " + ' '.join(events) + " timeout: " + str(timeout)) 193 start = os.times()[4] 194 try: 195 while True: 196 while self.pending(proc.stdout): 197 line = proc.stdout.readline() 198 if not line: 199 return None 200 line = line.decode() 201 logger.debug(line.strip('\n')) 202 for event in events: 203 if event in line: 204 return line 205 now = os.times()[4] 206 remaining = start + timeout - now 207 if remaining <= 0: 208 break 209 if not self.pending(proc.stdout, timeout=remaining): 210 break 211 except: 212 logger.debug(traceback.format_exc()) 213 pass 214 return None 215 216 def proc_stop(self, proc): 217 if not proc: 218 return 219 220 self.execute(["kill `cat " + proc.reaper_file + ".pid`"]) 221 self.execute(["rm", proc.reaper_file + ".pid"]) 222 self.execute(["rm", proc.reaper_file]) 223 self.local_execute(["rm", proc.reaper_file]) 224 proc.kill() 225 226 def proc_dump(self, proc): 227 if not proc: 228 return "" 229 return proc.stdout.read() 230 231 def execute_and_wait_event(self, command, events, timeout=10): 232 proc = None 233 ev = None 234 235 try: 236 proc = self.proc_run(command) 237 ev = self.proc_wait_event(proc, events, timeout) 238 except: 239 pass 240 241 self.proc_stop(proc) 242 return ev 243 244 def add_log(self, log_file): 245 self.logs.append(log_file) 246 247 def get_logs(self, local_log_dir=None): 248 for log in self.logs: 249 if local_log_dir: 250 self.local_execute(["scp", self.user + "@[" + self.host + "]:" + log, local_log_dir]) 251 self.execute(["rm", log]) 252 del self.logs[:] 253 254 def send_file(self, src, dst): 255 if self.host is None: 256 return 257 self.local_execute(["scp", src, 258 self.user + "@[" + self.host + "]:" + dst]) 259