Files
openairinterface5g/ci-scripts/cls_cmd.py
T
Robert Schmidt 670e1fb8b4 CI: remove unknown imports and uses of names
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
2024-10-22 09:51:23 +02:00

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)