2016-03-08 14:28:03 +01:00
|
|
|
# Host class
|
|
|
|
# Copyright (c) 2016, Qualcomm Atheros, Inc.
|
|
|
|
#
|
|
|
|
# This software may be distributed under the terms of the BSD license.
|
|
|
|
# See README for more details.
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import subprocess
|
|
|
|
import threading
|
2020-09-26 13:26:53 +02:00
|
|
|
import tempfile
|
2020-09-26 13:26:54 +02:00
|
|
|
import os
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
|
2016-06-23 19:16:35 +02:00
|
|
|
def remote_compatible(func):
|
|
|
|
func.remote_compatible = True
|
|
|
|
return func
|
|
|
|
|
2016-03-08 14:28:03 +01:00
|
|
|
def execute_thread(command, reply):
|
2016-04-29 07:07:32 +02:00
|
|
|
cmd = ' '.join(command)
|
|
|
|
logger.debug("thread run: " + cmd)
|
2020-09-26 13:26:53 +02:00
|
|
|
err = tempfile.TemporaryFile()
|
2016-03-08 14:28:03 +01:00
|
|
|
try:
|
2016-07-03 18:37:50 +02:00
|
|
|
status = 0
|
2020-09-26 13:26:53 +02:00
|
|
|
buf = subprocess.check_output(command, stderr=err).decode()
|
2016-03-08 14:28:03 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
status = e.returncode
|
2020-09-26 13:26:53 +02:00
|
|
|
err.seek(0)
|
|
|
|
buf = err.read()
|
|
|
|
err.close()
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
logger.debug("thread cmd: " + cmd)
|
|
|
|
logger.debug("thread exit status: " + str(status))
|
|
|
|
logger.debug("thread exit buf: " + str(buf))
|
|
|
|
reply.append(status)
|
|
|
|
reply.append(buf)
|
|
|
|
|
2020-09-26 13:26:54 +02:00
|
|
|
def gen_reaper_file(conf):
|
|
|
|
fd, filename = tempfile.mkstemp(dir='/tmp', prefix=conf + '-')
|
|
|
|
f = os.fdopen(fd, 'w')
|
|
|
|
|
|
|
|
f.write("#!/bin/sh\n")
|
|
|
|
f.write("name=\"$(basename $0)\"\n")
|
|
|
|
f.write("echo $$ > /tmp/$name.pid\n")
|
|
|
|
f.write("exec \"$@\"\n");
|
|
|
|
|
|
|
|
return filename;
|
|
|
|
|
2016-03-08 14:28:03 +01:00
|
|
|
class Host():
|
|
|
|
def __init__(self, host=None, ifname=None, port=None, name="", user="root"):
|
|
|
|
self.host = host
|
|
|
|
self.name = name
|
|
|
|
self.user = user
|
2016-04-29 07:07:34 +02:00
|
|
|
self.monitors = []
|
|
|
|
self.monitor_thread = None
|
|
|
|
self.logs = []
|
2016-03-08 14:28:03 +01:00
|
|
|
self.ifname = ifname
|
|
|
|
self.port = port
|
2016-04-29 07:07:35 +02:00
|
|
|
self.dev = None
|
tests: remote: Allow passing of parameters with monitor interface
This is mainly for standalone monitor in case we know and would like to
setup specific monitor configuration.
-m monitor:<chan>,<bw>, <cf1>, <cf2>:...
For example:
-m monitor:1,40,3,0
-m e4300:1,40,3,0:11,40,9,0
This also supports monitor with multiple interfaces (one pcap).
Signed-off-by: Janusz Dziedzic <janusz.dziedzic@gmail.com>
2020-09-26 13:26:59 +02:00
|
|
|
self.monitor_params = []
|
2016-03-08 14:28:03 +01:00
|
|
|
if self.name == "" and host != None:
|
|
|
|
self.name = host
|
|
|
|
|
|
|
|
def local_execute(self, command):
|
2016-04-24 11:28:18 +02:00
|
|
|
logger.debug("execute: " + str(command))
|
2020-09-26 13:26:53 +02:00
|
|
|
err = tempfile.TemporaryFile()
|
2016-03-08 14:28:03 +01:00
|
|
|
try:
|
2016-07-03 18:37:50 +02:00
|
|
|
status = 0
|
2020-09-26 13:26:53 +02:00
|
|
|
buf = subprocess.check_output(command, stderr=err)
|
2016-03-08 14:28:03 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
status = e.returncode
|
2020-09-26 13:26:53 +02:00
|
|
|
err.seek(0)
|
|
|
|
buf = err.read()
|
|
|
|
err.close()
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
logger.debug("status: " + str(status))
|
|
|
|
logger.debug("buf: " + str(buf))
|
2019-02-02 11:48:30 +01:00
|
|
|
return status, buf.decode()
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
def execute(self, command):
|
|
|
|
if self.host is None:
|
|
|
|
return self.local_execute(command)
|
|
|
|
|
2016-04-24 11:28:18 +02:00
|
|
|
cmd = ["ssh", self.user + "@" + self.host, ' '.join(command)]
|
2016-04-29 07:07:32 +02:00
|
|
|
_cmd = self.name + " execute: " + ' '.join(cmd)
|
2016-03-08 14:28:03 +01:00
|
|
|
logger.debug(_cmd)
|
2020-09-26 13:26:53 +02:00
|
|
|
err = tempfile.TemporaryFile()
|
2016-03-08 14:28:03 +01:00
|
|
|
try:
|
|
|
|
status = 0
|
2020-09-26 13:26:53 +02:00
|
|
|
buf = subprocess.check_output(cmd, stderr=err)
|
2016-03-08 14:28:03 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
status = e.returncode
|
2020-09-26 13:26:53 +02:00
|
|
|
err.seek(0)
|
|
|
|
buf = err.read()
|
|
|
|
err.close()
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
logger.debug(self.name + " status: " + str(status))
|
|
|
|
logger.debug(self.name + " buf: " + str(buf))
|
2019-02-02 11:48:30 +01:00
|
|
|
return status, buf.decode()
|
2016-03-08 14:28:03 +01:00
|
|
|
|
|
|
|
# async execute
|
2020-11-07 11:49:14 +01:00
|
|
|
def thread_run(self, command, res, use_reaper=True):
|
2020-09-26 13:26:54 +02:00
|
|
|
if use_reaper:
|
|
|
|
filename = gen_reaper_file("reaper")
|
|
|
|
self.send_file(filename, filename)
|
|
|
|
self.execute(["chmod", "755", filename])
|
|
|
|
_command = [filename] + command
|
|
|
|
else:
|
|
|
|
filename = ""
|
|
|
|
_command = command
|
|
|
|
|
2016-03-08 14:28:03 +01:00
|
|
|
if self.host is None:
|
2020-09-26 13:26:54 +02:00
|
|
|
cmd = _command
|
2016-03-08 14:28:03 +01:00
|
|
|
else:
|
2020-09-26 13:26:54 +02:00
|
|
|
cmd = ["ssh", self.user + "@" + self.host, ' '.join(_command)]
|
2020-11-07 11:49:14 +01:00
|
|
|
_cmd = self.name + " thread_run: " + ' '.join(cmd)
|
2016-03-08 14:28:03 +01:00
|
|
|
logger.debug(_cmd)
|
2020-09-26 13:26:54 +02:00
|
|
|
t = threading.Thread(target=execute_thread, name=filename, args=(cmd, res))
|
2016-03-08 14:28:03 +01:00
|
|
|
t.start()
|
|
|
|
return t
|
|
|
|
|
2020-11-07 11:49:14 +01:00
|
|
|
def thread_stop(self, t):
|
2020-09-26 13:26:54 +02:00
|
|
|
if t.name.find("reaper") == -1:
|
|
|
|
raise Exception("use_reaper required")
|
|
|
|
|
|
|
|
pid_file = t.name + ".pid"
|
|
|
|
|
|
|
|
if t.isAlive():
|
|
|
|
cmd = ["kill `cat " + pid_file + "`"]
|
|
|
|
self.execute(cmd)
|
|
|
|
|
|
|
|
# try again
|
2021-01-10 16:50:46 +01:00
|
|
|
self.thread_wait(t, 5)
|
2020-09-26 13:26:54 +02:00
|
|
|
if t.isAlive():
|
|
|
|
cmd = ["kill `cat " + pid_file + "`"]
|
|
|
|
self.execute(cmd)
|
|
|
|
|
|
|
|
# try with -9
|
2021-01-10 16:50:46 +01:00
|
|
|
self.thread_wait(t, 5)
|
2020-09-26 13:26:54 +02:00
|
|
|
if t.isAlive():
|
|
|
|
cmd = ["kill -9 `cat " + pid_file + "`"]
|
|
|
|
self.execute(cmd)
|
|
|
|
|
2021-01-10 16:50:46 +01:00
|
|
|
self.thread_wait(t, 5)
|
2020-09-26 13:26:54 +02:00
|
|
|
if t.isAlive():
|
|
|
|
raise Exception("thread still alive")
|
|
|
|
|
|
|
|
self.execute(["rm", pid_file])
|
|
|
|
self.execute(["rm", t.name])
|
2020-11-07 11:49:15 +01:00
|
|
|
self.local_execute(["rm", t.name])
|
2020-09-26 13:26:54 +02:00
|
|
|
|
2020-11-07 11:49:14 +01:00
|
|
|
def thread_wait(self, t, wait=None):
|
2016-03-08 14:28:03 +01:00
|
|
|
if wait == None:
|
|
|
|
wait_str = "infinite"
|
|
|
|
else:
|
|
|
|
wait_str = str(wait) + "s"
|
|
|
|
|
2020-11-07 11:49:14 +01:00
|
|
|
logger.debug(self.name + " thread_wait(" + wait_str + "): ")
|
2016-03-08 14:28:03 +01:00
|
|
|
if t.isAlive():
|
|
|
|
t.join(wait)
|
2016-04-29 07:07:34 +02:00
|
|
|
|
2020-11-07 11:49:15 +01:00
|
|
|
def pending(self, s, timeout=0):
|
|
|
|
[r, w, e] = select.select([s], [], [], timeout)
|
|
|
|
if r:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def proc_run(self, command):
|
|
|
|
filename = gen_reaper_file("reaper")
|
|
|
|
self.send_file(filename, filename)
|
|
|
|
self.execute(["chmod", "755", filename])
|
|
|
|
_command = [filename] + command
|
|
|
|
|
|
|
|
if self.host:
|
|
|
|
cmd = ["ssh", self.user + "@" + self.host, ' '.join(_command)]
|
|
|
|
else:
|
|
|
|
cmd = _command
|
|
|
|
|
|
|
|
_cmd = self.name + " proc_run: " + ' '.join(cmd)
|
|
|
|
logger.debug(_cmd)
|
|
|
|
err = tempfile.TemporaryFile()
|
|
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=err)
|
|
|
|
proc.reaper_file = filename
|
|
|
|
return proc
|
|
|
|
|
|
|
|
def proc_wait_event(self, proc, events, timeout=10):
|
|
|
|
if not isinstance(events, list):
|
|
|
|
raise Exception("proc_wait_event() events not a list")
|
|
|
|
|
|
|
|
logger.debug(self.name + " proc_wait_event: " + ' '.join(events) + " timeout: " + str(timeout))
|
|
|
|
start = os.times()[4]
|
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
while self.pending(proc.stdout):
|
|
|
|
line = proc.stdout.readline()
|
|
|
|
if not line:
|
|
|
|
return None
|
|
|
|
line = line.decode()
|
|
|
|
logger.debug(line.strip('\n'))
|
|
|
|
for event in events:
|
|
|
|
if event in line:
|
|
|
|
return line
|
|
|
|
now = os.times()[4]
|
|
|
|
remaining = start + timeout - now
|
|
|
|
if remaining <= 0:
|
|
|
|
break
|
|
|
|
if not self.pending(proc.stdout, timeout=remaining):
|
|
|
|
break
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
|
|
def proc_stop(self, proc):
|
|
|
|
if not proc:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.execute(["kill `cat " + proc.reaper_file + ".pid`"])
|
|
|
|
self.execute(["rm", proc.reaper_file + ".pid"])
|
|
|
|
self.execute(["rm", proc.reaper_file])
|
|
|
|
self.local_execute(["rm", proc.reaper_file])
|
|
|
|
proc.kill()
|
|
|
|
|
|
|
|
def proc_dump(self, proc):
|
|
|
|
if not proc:
|
|
|
|
return ""
|
|
|
|
return proc.stdout.read()
|
|
|
|
|
|
|
|
def execute_and_wait_event(self, command, events, timeout=10):
|
|
|
|
proc = None
|
|
|
|
ev = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
proc = self.proc_run(command)
|
|
|
|
ev = self.proc_wait_event(proc, events, timeout)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
self.proc_stop(proc)
|
|
|
|
return ev
|
|
|
|
|
2016-05-19 15:06:44 +02:00
|
|
|
def add_log(self, log_file):
|
|
|
|
self.logs.append(log_file)
|
|
|
|
|
2016-04-29 07:07:34 +02:00
|
|
|
def get_logs(self, local_log_dir=None):
|
|
|
|
for log in self.logs:
|
|
|
|
if local_log_dir:
|
|
|
|
self.local_execute(["scp", self.user + "@[" + self.host + "]:" + log, local_log_dir])
|
|
|
|
self.execute(["rm", log])
|
|
|
|
del self.logs[:]
|
2020-01-12 23:02:21 +01:00
|
|
|
|
|
|
|
def send_file(self, src, dst):
|
|
|
|
if self.host is None:
|
|
|
|
return
|
|
|
|
self.local_execute(["scp", src,
|
|
|
|
self.user + "@[" + self.host + "]:" + dst])
|