# SPDX-License-Identifier: LicenseRef-CSSL-1.0 #--------------------------------------------------------------------- # # Required Python Version # Python 3.x # #--------------------------------------------------------------------- import logging import yaml import re import cls_cmd from cls_ci_helper import archiveArtifact def listify(s): if s is None: return None if isinstance(s, list): return [str(x) for x in s] return [str(s)] class CoreNetwork: def __init__(self, cn_name, node=None, d=None, filename="ci_infra.yaml"): with open(filename, "r") as f: all_cns = yaml.load(f, Loader=yaml.FullLoader) # TODO why full? c = all_cns.get(cn_name) if c is None: raise Exception(f'no such core network name "{cn_name}" in "{filename}"') self._cn_name = cn_name self._host = c.get('Host').strip() if self._host == "%%current_host%%": if node is None: raise Exception(f"core network {cn_name} requires node, but none provided (cannot replace %%current_host%%)") self._host = node if d is not None: raise Exception("directory handling not implemented") self._deploy = listify(c.get('Deploy')) self._undeploy = listify(c.get('Undeploy')) self._logCollect = listify(c.get('LogCollect')) # check if at least one command contains "%%log_dir%%" if self._logCollect and not any(filter(lambda s: "%%log_dir%%" in s, self._logCollect)): raise ValueError(f"(At least one) LogCollect expression for {cn_name} must contain \"%%log_dir%%\"") self._getNetwork = listify(c.get('NetworkScript')) self._cmd_prefix = c.get('CmdPrefix') iperf = c.get('RunIperf3Server') self._run_iperf = bool(iperf if iperf is not None else True) logging.info(f'initialized core {self} from {filename}') def __str__(self): return f"{self._cn_name}@{self._host}" def __repr__(self): return self.__str__() def _exec_script(host, line, silent): # take off "!", split in words words = line[1:].strip().split(" ") script_name = words[0] options = " ".join(words[1:]) with cls_cmd.getConnection(host) as c: ret = c.exec_script(script_name, 300, parameters=options, silent=silent) return ret def _command(self, cmd_list, must_succeed=False, silent=False): succeeded = True output = "" with cls_cmd.getConnection(self._host) as c: for cmd in cmd_list: cmd = cmd.strip() if cmd.startswith("!"): ret = CoreNetwork._exec_script(self._host, cmd, silent=silent) else: ret = c.run(cmd, silent=silent) output += ret.stdout if ret.returncode != 0: logging.warn(f"cmd \"{cmd}\" returned code {ret.returncode}, stdout {ret.stdout}") if ret.returncode != 0 and must_succeed: succeeded = False break return succeeded, output def deploy(self): # we first undeploy to make sure the core has been stopped logging.info(f'undeploy core network {self} before deployment') self._command(self._undeploy) logging.info(f'deploy core network {self}') success, output = self._command(self._deploy, must_succeed=True) if not success: logging.error(f'failure during deployment of core network {self}:\n{output}') return False, output logging.info(f'retrieve IP address') ip = self.getIP() if ip is None: logging.error(f'could not retrieve pingable address of core network {self}') return False, output logging.info(f'deployed core network {self}, pingable IP address {ip}') return True, output def _collect_logs(self, ctx): remote_dir = "/tmp/cn-undeploy-logs" with cls_cmd.getConnection(self._host) as c: # create a directory for log collection c.run(f'rm -rf {remote_dir}') ret = c.run(f'mkdir {remote_dir}') if ret.returncode != 0: logging.error("cannot create directory for log collection") return [] # inform core network implementation where to store logs # (remote_dir), and trigger log collection log_cmd = [s.replace('%%log_dir%%', remote_dir) for s in self._logCollect] self._command(log_cmd) # enumerate collected files ret = c.run(f'ls {remote_dir}/*') if ret.returncode != 0: logging.error("cannot enumerate log files") return [] log_files = [] # copy them to the executor one by one for f in ret.stdout.split("\n"): name = archiveArtifact(c, ctx, f) log_files.append(name) c.run(f'rm -rf {remote_dir}') return log_files def undeploy(self, ctx=None): log_files = [] if ctx is not None: log_files = self._collect_logs(ctx) else: logging.warning("no directory for log collection specified, cannot retrieve core network logs") logging.info(f'undeploy core network {self}') _, output = self._command(self._undeploy) return log_files, output def getIP(self): success, output = self._command(self._getNetwork) result = re.search(r'inet (?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', output) if success and result and result.group('ip'): return result.group('ip') return None def getCmdPrefix(self): return self._cmd_prefix or "" def getName(self): return self._cn_name def getHost(self): return self._host def runIperf3Server(self): return self._run_iperf