pyflakes warns about various errors, addressed here. For cls_cluster.py,
remove (unknown) sys.exit and replace through Exception, as sys.exit()
has the unwanted effect that the CI script would stop. Instead, raise
an Exception that would show the problem in the HTML, and ensures that
the script runs until the end.
These are the warnings flagged by pyflakes.
cls_cluster.py:297:4: undefined name 'sys'
cls_containerize.py:39:1: 'subprocess' imported but unused
cls_containerize.py:41:1: 'threading' imported but unused
cls_containerize.py:43:1: 'multiprocessing.Process' imported but unused
cls_containerize.py:43:1: 'multiprocessing.Lock' imported but unused
cls_containerize.py:43:1: 'multiprocessing.SimpleQueue' imported but unused
cls_containerize.py:49:1: 'cls_cluster as OC' imported but unused
cls_containerize.py:50:1: redefinition of unused 'cls_cmd' from line 42
cls_module.py:28:1: 'os' imported but unused
cls_module.py:29:1: 'sys' imported but unused
cls_module.py:34:1: 'subprocess' imported but unused
cls_module.py:35:1: 'datetime.datetime' imported but unused
cls_module.py:211:11: undefined name 'sshconnection'
cls_oaicitest.py:37:1: 'pexpect' imported but unused
cls_oaicitest.py:47:1: 'cls_cluster as OC' imported but unused
cls_oaicitest.py:54:1: 'matplotlib.pyplot as plt' imported but unused
cls_oaicitest.py:55:1: 'numpy as np' imported but unused
cls_oai_html.py:34:1: 'sys' imported but unused
cls_oai_html.py:41:1: 'multiprocessing.Process' imported but unused
cls_oai_html.py:41:1: 'multiprocessing.Lock' imported but unused
cls_oai_html.py:41:1: 'multiprocessing.SimpleQueue' imported but unused
cls_physim.py:37:1: 'multiprocessing.SimpleQueue' imported but unused
cls_static_code_analysis.py:39:1: 'time' imported but unused
cls_static_code_analysis.py:40:1: 'multiprocessing.Process' imported but unused
cls_static_code_analysis.py:40:1: 'multiprocessing.Lock' imported but unused
cls_static_code_analysis.py:40:1: 'multiprocessing.SimpleQueue' imported but unused
epc.py:41:1: 'multiprocessing.Process' imported but unused
epc.py:41:1: 'multiprocessing.Lock' imported but unused
epc.py:41:1: 'multiprocessing.SimpleQueue' imported but unused
main.py:60:1: 'pexpect' imported but unused
main.py:66:1: 'datetime' imported but unused
main.py:68:1: redefinition of unused 'subprocess' from line 63
main.py:70:1: 'multiprocessing.Process' imported but unused
main.py:70:1: 'multiprocessing.Lock' imported but unused
main.py:70:1: 'multiprocessing.SimpleQueue' imported but unused
provideUniqueImageTag.py:2:1: 'os' imported but unused
327 lines
11 KiB
Python
327 lines
11 KiB
Python
#/*
|
|
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
|
# * contributor license agreements. See the NOTICE file distributed with
|
|
# * this work for additional information regarding copyright ownership.
|
|
# * The OpenAirInterface Software Alliance licenses this file to You under
|
|
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
|
# * except in compliance with the License.
|
|
# * You may obtain a copy of the License at
|
|
# *
|
|
# * http://www.openairinterface.org/?page_id=698
|
|
# *
|
|
# * Unless required by applicable law or agreed to in writing, software
|
|
# * distributed under the License is distributed on an "AS IS" BASIS,
|
|
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# * See the License for the specific language governing permissions and
|
|
# * limitations under the License.
|
|
# *-------------------------------------------------------------------------------
|
|
# * For more information about the OpenAirInterface (OAI) Software Alliance:
|
|
# * contact@openairinterface.org
|
|
# */
|
|
#---------------------------------------------------------------------
|
|
#
|
|
# Required Python Version
|
|
# Python 3.x
|
|
#
|
|
#---------------------------------------------------------------------
|
|
|
|
import abc
|
|
import logging
|
|
import subprocess as sp
|
|
import os
|
|
import paramiko
|
|
import uuid
|
|
import time
|
|
|
|
SSHTIMEOUT=7
|
|
|
|
def is_local(host):
|
|
return host is None or host.lower() in ["", "none", "localhost"]
|
|
|
|
# helper that returns either LocalCmd or RemoteCmd based on passed host name
|
|
def getConnection(host, d=None):
|
|
if is_local(host):
|
|
return LocalCmd(d=d)
|
|
else:
|
|
return RemoteCmd(host, d=d)
|
|
|
|
def runScript(host, path, timeout, parameters=None, redirect=None, silent=False):
|
|
if is_local(host):
|
|
return LocalCmd.exec_script(path, timeout, parameters, redirect, silent)
|
|
else:
|
|
return RemoteCmd.exec_script(host, path, timeout, parameters, redirect, silent)
|
|
|
|
# provides a partial interface for the legacy SSHconnection class (getBefore(), command())
|
|
class Cmd(metaclass=abc.ABCMeta):
|
|
def cd(self, d, silent=False):
|
|
if d == None or d == '':
|
|
self.cwd = None
|
|
elif d[0] == '/':
|
|
self.cwd = d
|
|
else:
|
|
if not self.cwd:
|
|
# no cwd set: get current working directory
|
|
self.cwd = self.run('pwd').stdout.strip()
|
|
self.cwd += f"/{d}"
|
|
if not silent:
|
|
logging.debug(f'cd {self.cwd}')
|
|
|
|
@abc.abstractmethod
|
|
def exec_script(path, timeout, parameters=None, redirect=None, silent=False):
|
|
return
|
|
|
|
@abc.abstractmethod
|
|
def run(self, line, timeout=300, silent=False):
|
|
return
|
|
|
|
def command(self, commandline, expectedline=None, timeout=300, silent=False, resync=False):
|
|
splitted = commandline.split(' ')
|
|
if splitted[0] == 'cd':
|
|
self.cd(' '.join(splitted[1:]), silent)
|
|
else:
|
|
self.run(commandline, timeout, silent)
|
|
return 0
|
|
|
|
@abc.abstractmethod
|
|
def close(self):
|
|
return
|
|
|
|
@abc.abstractmethod
|
|
def getBefore(self):
|
|
return
|
|
|
|
@abc.abstractmethod
|
|
def copyin(self, scpIp, scpUser, scpPw, src, tgt):
|
|
return
|
|
|
|
@abc.abstractmethod
|
|
def copyout(self, scpIp, scpUser, scpPw, src, tgt):
|
|
return
|
|
|
|
class LocalCmd(Cmd):
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
self.close()
|
|
|
|
def __init__(self, d = None):
|
|
self.cwd = d
|
|
if self.cwd is not None:
|
|
logging.debug(f'Working dir is {self.cwd}')
|
|
self.cp = sp.CompletedProcess(args='', returncode=0, stdout='')
|
|
|
|
def exec_script(path, timeout, parameters=None, redirect=None, silent=False):
|
|
if redirect and not redirect.startswith("/"):
|
|
raise ValueError(f"redirect must be absolute, but is {redirect}")
|
|
c = f"{path} {parameters}" if parameters else path
|
|
if not redirect:
|
|
ret = sp.run(c, shell=True, timeout=timeout, stdout=sp.PIPE, stderr=sp.STDOUT)
|
|
ret.stdout = ret.stdout.decode('utf-8').strip()
|
|
else:
|
|
with open(redirect, "w") as f:
|
|
ret = sp.run(c, shell=True, timeout=timeout, stdout=f, stderr=f)
|
|
ret.args += f" &> {redirect}"
|
|
ret.stdout = ""
|
|
if not silent:
|
|
logging.info(f"local> {ret.args}")
|
|
return ret
|
|
|
|
def run(self, line, timeout=300, silent=False, reportNonZero=True):
|
|
if not silent:
|
|
logging.info(f"local> {line}")
|
|
try:
|
|
if line.strip().endswith('&'):
|
|
# if we wait for stdout, subprocess does not return before the end of the command
|
|
# however, we don't want to wait for commands with &, so just return fake command
|
|
ret = sp.run(line, shell=True, cwd=self.cwd, timeout=5, executable='/bin/bash')
|
|
time.sleep(0.1)
|
|
else:
|
|
ret = sp.run(line, shell=True, cwd=self.cwd, stdout=sp.PIPE, stderr=sp.STDOUT, timeout=timeout, executable='/bin/bash')
|
|
except Exception as e:
|
|
ret = sp.CompletedProcess(args=line, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8'))
|
|
if ret.stdout is None:
|
|
ret.stdout = b''
|
|
ret.stdout = ret.stdout.decode('utf-8').strip()
|
|
if reportNonZero and ret.returncode != 0:
|
|
logging.warning(f'command "{ret.args}" returned non-zero returncode {ret.returncode}: output:\n{ret.stdout}')
|
|
self.cp = ret
|
|
return ret
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
def getBefore(self):
|
|
return self.cp.stdout
|
|
|
|
def copyin(self, src, tgt, recursive=False):
|
|
if src[0] != '/' or tgt[0] != '/':
|
|
raise Exception(f'support only absolute file paths (src {src} tgt {tgt})!')
|
|
if src == tgt:
|
|
return # nothing to copy, file is already where it should go
|
|
opt = '-r' if recursive else ''
|
|
return self.run(f'cp {opt} {src} {tgt}').returncode == 0
|
|
|
|
def copyout(self, src, tgt, recursive=False):
|
|
return self.copyin(src, tgt, recursive)
|
|
|
|
def PutFile(client, src, tgt):
|
|
success = True
|
|
sftp = client.open_sftp()
|
|
try:
|
|
sftp.put(src, tgt)
|
|
except FileNotFoundError as error:
|
|
logging.error(f"error while putting {src}: {error}")
|
|
success = False
|
|
sftp.close()
|
|
return success
|
|
|
|
def GetFile(client, src, tgt):
|
|
success = True
|
|
sftp = client.open_sftp()
|
|
try:
|
|
sftp.get(src, tgt)
|
|
except FileNotFoundError as error:
|
|
logging.error(f"error while getting {src}: {error}")
|
|
success = False
|
|
sftp.close()
|
|
return success
|
|
|
|
class RemoteCmd(Cmd):
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
self.close()
|
|
|
|
def __init__(self, hostname, d=None):
|
|
cIdx = 0
|
|
self.hostname = hostname
|
|
self.client = RemoteCmd._ssh_init()
|
|
cfg = RemoteCmd._lookup_ssh_config(hostname)
|
|
self.cwd = d
|
|
self.cp = sp.CompletedProcess(args='', returncode=0, stdout='')
|
|
while cIdx < 3:
|
|
try:
|
|
self.client.connect(**cfg)
|
|
return
|
|
except:
|
|
logging.error(f'Could not connect to {hostname}, tried for {cIdx} time')
|
|
cIdx +=1
|
|
raise Exception ("Error: max retries, did not connect to host")
|
|
|
|
def _ssh_init():
|
|
logging.getLogger('paramiko').setLevel(logging.ERROR) # prevent spamming through Paramiko
|
|
client = paramiko.SSHClient()
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
return client
|
|
|
|
def _lookup_ssh_config(hostname):
|
|
ssh_config = paramiko.SSHConfig()
|
|
user_config_file = os.path.expanduser("~/.ssh/config")
|
|
if os.path.exists(user_config_file):
|
|
with open(user_config_file) as f:
|
|
ssh_config.parse(f)
|
|
else:
|
|
raise FileNotFoundError('class needs SSH config at ~/.ssh/config')
|
|
ucfg = ssh_config.lookup(hostname)
|
|
if 'identityfile' not in ucfg or 'user' not in ucfg:
|
|
raise KeyError(f'no identityfile or user in SSH config for host {hostname}')
|
|
cfg = {'hostname':hostname, 'username':ucfg['user'], 'key_filename':ucfg['identityfile'], 'timeout':SSHTIMEOUT}
|
|
if 'hostname' in ucfg:
|
|
cfg['hostname'] = ucfg['hostname'] # override user-given hostname with what is in config
|
|
if 'port' in ucfg:
|
|
cfg['port'] = int(ucfg['port'])
|
|
if 'proxycommand' in ucfg:
|
|
cfg['sock'] = paramiko.ProxyCommand(ucfg['proxycommand'])
|
|
return cfg
|
|
|
|
def exec_script(host, path, timeout, parameters=None, redirect=None, silent=False):
|
|
if redirect and not redirect.startswith("/"):
|
|
raise ValueError(f"redirect must be absolute, but is {redirect}")
|
|
p = parameters if parameters else ""
|
|
r = f"> {redirect}" if redirect else ""
|
|
if not silent:
|
|
logging.info(f"local> ssh {host} bash -s {p} < {path} {r} # {path} from localhost")
|
|
client = RemoteCmd._ssh_init()
|
|
cfg = RemoteCmd._lookup_ssh_config(host)
|
|
client.connect(**cfg)
|
|
bash_opt = 'BASH_XTRACEFD=1' # write bash set -x output to stdout, see bash(1)
|
|
stdin, stdout, stderr = client.exec_command(f"{bash_opt} bash -s {p} {r}", timeout=timeout)
|
|
# open() the file f at path, read() it and write() it into the stdin of the bash -s cmd
|
|
with open(path) as f:
|
|
stdin.write(f.read())
|
|
stdin.close()
|
|
cmd = path
|
|
if parameters: cmd += f" {parameters}"
|
|
if redirect: cmd += f" &> {redirect}"
|
|
ret = sp.CompletedProcess(args=cmd, returncode=stdout.channel.recv_exit_status(), stdout=stdout.read(size=None) + stderr.read(size=None))
|
|
ret.stdout = ret.stdout.decode('utf-8').strip()
|
|
client.close()
|
|
return ret
|
|
|
|
def run(self, line, timeout=300, silent=False, reportNonZero=True):
|
|
if not silent:
|
|
logging.info(f"ssh[{self.hostname}]> {line}")
|
|
if self.cwd:
|
|
line = f"cd {self.cwd} && {line}"
|
|
try:
|
|
if line.strip().endswith('&'):
|
|
# if we wait for stdout, Paramiko does not return before the end of the command
|
|
# however, we don't want to wait for commands with &, so just return fake command
|
|
self.client.exec_command(line, timeout = 5)
|
|
ret = sp.CompletedProcess(args=line, returncode=0, stdout=b'')
|
|
time.sleep(0.1)
|
|
else:
|
|
stdin, stdout, stderr = self.client.exec_command(line, timeout=timeout)
|
|
ret = sp.CompletedProcess(args=line, returncode=stdout.channel.recv_exit_status(), stdout=stdout.read(size=None) + stderr.read(size=None))
|
|
except Exception as e:
|
|
ret = sp.CompletedProcess(args=line, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8'))
|
|
ret.stdout = ret.stdout.decode('utf-8').strip()
|
|
if reportNonZero and ret.returncode != 0:
|
|
logging.warning(f'command "{line}" returned non-zero returncode {ret.returncode}: output:\n{ret.stdout}')
|
|
self.cp = ret
|
|
return ret
|
|
|
|
def close(self):
|
|
self.client.close()
|
|
|
|
def getBefore(self):
|
|
return self.cp.stdout
|
|
|
|
# if recursive is True, tgt must be a directory (and src is file or directory)
|
|
# if recursive is False, tgt and src must be a file name
|
|
def copyout(self, src, tgt, recursive=False):
|
|
logging.debug(f"copyout: local:{src} -> {self.hostname}:{tgt}")
|
|
if recursive:
|
|
tmpfile = f"{uuid.uuid4()}.tar"
|
|
abstmpfile = f"/tmp/{tmpfile}"
|
|
with LocalCmd() as cmd:
|
|
if cmd.run(f"tar -cf {abstmpfile} {src}").returncode != 0:
|
|
return False
|
|
if not PutFile(self.client, abstmpfile, abstmpfile):
|
|
return False
|
|
cmd.run(f"rm {abstmpfile}")
|
|
ret = self.run(f"mv {abstmpfile} {tgt}; cd {tgt} && tar -xf {tmpfile} && rm {tmpfile}")
|
|
return ret.returncode == 0
|
|
else:
|
|
return PutFile(self.client, src, tgt)
|
|
|
|
# if recursive is True, tgt must be a directory (and src is file or directory)
|
|
# if recursive is False, tgt and src must be a file name
|
|
def copyin(self, src, tgt, recursive=False):
|
|
logging.debug(f"copyin: {self.hostname}:{src} -> local:{tgt}")
|
|
if recursive:
|
|
tmpfile = f"{uuid.uuid4()}.tar"
|
|
abstmpfile = f"/tmp/{tmpfile}"
|
|
if self.run(f"tar -cf {abstmpfile} {src}").returncode != 0:
|
|
return False
|
|
if not GetFile(self.client, abstmpfile, abstmpfile):
|
|
return False
|
|
self.run(f"rm {abstmpfile}")
|
|
with LocalCmd() as cmd:
|
|
ret = cmd.run(f"mv {abstmpfile} {tgt}; cd {tgt} && tar -xf {tmpfile} && rm {tmpfile}")
|
|
return ret.returncode == 0
|
|
else:
|
|
return GetFile(self.client, src, tgt)
|