class ZTP(host_service.HostModule): """DBus endpoint that executes ZTP related commands """ @staticmethod def _run_command(commands): """Run a ZTP command""" cmd = ['/usr/bin/ztp'] if isinstance(commands, list): cmd.extend(commands) else: cmd.append(commands) try: rc = 0 output = subprocess.check_output(cmd) except subprocess.CalledProcessError as err: rc = err.returncode output = "" return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='') def enable(self): self._run_command("enable") @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='s') def getcfg(self): ztp_cfg = ZTPCfg() ad = getCfg('admin-mode', ztp_cfg=ztp_cfg) if ad == False: return "disabled" else: return "enabled" @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='') def disable(self): self._run_command(["disable", "-y"]) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='s') def status(self): print("Calling ZTP Status") b = ztp_status() print("Returning", b) return b
class IMAGE_MGMT(host_service.HostModule): """DBus endpoint that executes CFG_MGMT related commands """ @staticmethod def _run_command(options): """ Run config mgmt command """ cmd = ['/usr/bin/sonic_installer'] for x in options: cmd.append(str(x)) output = "" try: print("cmd", cmd) rc = 0 output = subprocess.check_output(cmd) print('Output -> ', output) except subprocess.CalledProcessError as err: print("Exception when calling get_sonic_error -> %s\n" % (err)) rc = err.returncode output = err.output return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def action(self, options): return IMAAGE_MGMT._run_command(options)
class Showtech(host_service.HostModule): """DBus endpoint that executes the "show techsupport" command """ @host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is') def info(self, date): print("Host side: Running show techsupport") cmd = ['/usr/bin/generate_dump'] if date: cmd.append("-s") cmd.append(date) try: rc = 0 output = subprocess.check_output(cmd) except subprocess.CalledProcessError as err: rc = err.returncode output = "" print("Host side: Failed", rc) return rc, output output_string = output.decode("utf-8") output_file_match = re.search('\/var\/.*dump.*\.gz', output_string) if output_file_match is not None: output_filename = output_file_match.group() else: output_filename = "" return rc, output_filename
class CFG_MGMT(host_service.HostModule): """DBus endpoint that executes CFG_MGMT related commands """ @staticmethod def _run_command(commands, options): """ Run config mgmt command """ cmd = ['/usr/bin/config'] if isinstance(commands, list): cmd.extend(commands) else: cmd.append(commands) for x in options: cmd.append(str(x)) output = "" try: print("cmd", cmd) rc = 0 output = subprocess.check_output(cmd) print('Output -> ', output) except subprocess.CalledProcessError as err: print("Exception when calling get_sonic_error -> %s\n" % (err)) rc = err.returncode output = err.output return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def save(self, options): return CFG_MGMT._run_command(["save", "-y"], options) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def reload(self, options): return CFG_MGMT._run_command(["reload", "-y"], options) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def load(self, options): return CFG_MGMT._run_command(["load", "-y"], options)
class Showtech(host_service.HostModule): """DBus endpoint that executes the "show techsupport" command """ @host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is') def info(self, date): ERROR_TAR_FAILED = 5 ERROR_PROCFS_SAVE_FAILED = 6 ERROR_INVALID_ARGUMENT = 10 err_dict = { ERROR_INVALID_ARGUMENT: 'Invalid input: Incorrect DateTime format', ERROR_TAR_FAILED: 'Failure saving information into compressed output file', ERROR_PROCFS_SAVE_FAILED: 'Saving of process information failed' } cmd = ['/usr/local/bin/generate_dump'] if date: cmd.append("-s") cmd.append(date) try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) except subprocess.CalledProcessError as err: errmsg = err_dict.get(err.returncode) if errmsg is None: output = 'Error: Failure code {:-5}'.format(err.returncode) else: output = errmsg print("%Error: Host side: Failed: " + str(err.returncode)) return err.returncode, output output_file_match = re.search('\/var\/.*dump.*\.gz', result.stdout) output_filename = output_file_match.group() return result.returncode, output_filename
class FETCH_ENVIRONMENT(host_service.HostModule): """DBus endpoint that executes CFG_MGMT related commands """ @staticmethod def _run_command(options): """ Run show environment command """ cmd = ['/usr/bin/show', 'environment'] output = "" try: rc = 0 output = subprocess.check_output(cmd) print('Output -> ', output) except subprocess.CalledProcessError as err: print("Exception when calling get_sonic_error -> %s\n" % (err)) rc = err.returncode output = err.output return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def action(self, options): return FETCH_ENVIRONMENT._run_command(options)
class CFG_MGMT(host_service.HostModule): """DBus endpoint that executes CFG_MGMT related commands """ @staticmethod def _run_command(commands, options): """ Run config mgmt command """ cmd = ['/usr/bin/config'] if isinstance(commands, list): cmd.extend(commands) else: cmd.append(commands) cmd.extend(str(x) for x in options) output = "" try: print("cmd", cmd) rc = 0 output = subprocess.check_output(cmd) print('Output -> ', output) except subprocess.CalledProcessError as err: print("Exception when calling get_sonic_error -> %s\n" % (err)) rc = err.returncode output = err.output return rc, output @staticmethod def _docker_copy(container, _file, tempdir, copy_to_container): # Copy files/folders to/from containers if copy_to_container: src = os.path.join(tempdir, container, _file.lstrip('/')) dst = container + ':' + _file else: src = container + ':' + _file dst = os.path.join(tempdir, container, _file.lstrip('/')) try: os.makedirs(os.path.dirname(dst), exist_ok=True) except OSError: pass cmd = ['/usr/bin/docker', 'cp', '-aL', src, dst] try: rc = 0 subprocess.check_call(cmd) except subprocess.CalledProcessError as err: rc = err.returncode return rc @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def save(self, options): """Save configuration""" tempdir = tempfile.mkdtemp() try: config = self._load_config() for module, modcfg in config.items(): destdir = os.path.join(tempdir, module) os.mkdir(destdir) container = modcfg.get('container', None) if container is None: # Copy from host for _file in modcfg.get('files', []): # Copy individual files src = _file dst = os.path.join(destdir, os.path.dirname(_file.lstrip('/'))) try: os.makedirs(dst, exist_ok=True) except OSError: # dir already exists pass shutil.copy(src, dst) for folder in modcfg.get('folders', []): # Copy directory trees src = folder dst = os.path.join(destdir, folder.lstrip('/')) shutil.copytree(src, dst, symlinks=True) else: # Copy from container for _file in modcfg.get('files', []): self._docker_copy(container, _file, tempdir, False) for folder in modcfg.get('folders', []): self._docker_copy(container, folder, tempdir, False) tfile = os.path.join(tempdir, 'config_db.json') rc, output = CFG_MGMT._run_command(["save", "-y"], [tfile]) if rc != 0: return rc, output # Copy config_db.json to /etc/sonic shutil.copy(tfile, '/etc/sonic') if options: filename = options[0] else: filename = DEFAULT_FILE with tarfile.open(filename, 'w') as tf: tf.add(tempdir, arcname='.') finally: shutil.rmtree(tempdir, ignore_errors=True) return 0, filename def _load_from_tar(self, load_type, options): if options: filename = options[0] else: filename = DEFAULT_FILE tempdir = tempfile.mkdtemp() try: with tarfile.open(filename, 'r') as tf: tf.extractall(tempdir) config = self._load_config() for module, modcfg in config.items(): container = modcfg.get('container', None) srcdir = os.path.join(tempdir, module) if container is None: # Copy from host for _file in modcfg.get('files', []): # Copy individual files src = os.path.join(srcdir, _file.lstrip('/')) dst = os.path.dirname(_file) try: os.makedirs(dst, exist_ok=True) except OSError: # dir already exists pass shutil.copy(src, dst) for folder in modcfg.get('folders', []): # Copy directory trees src = os.path.join(srcdir, folder.lstrip('/')) dst = folder try: # We have to shell out to /bin/cp, since copytree # will abort if the destination already exists cmd = ['/bin/cp', '-a', src, dst] subprocess.check_call(cmd) except subprocess.CalledProcessError as err: pass else: # Copy to container for _file in modcfg.get('files', []): self._docker_copy(container, _file, tempdir, True) for folder in modcfg.get('folders', []): self._docker_copy(container, folder, tempdir, True) config_db_json = os.path.join(tempdir, 'config_db.json') rc, output = CFG_MGMT._run_command([load_type, "-y"], [config_db_json]) except (tarfile.ReadError, FileNotFoundError): # Either file is not found or file is not a tarfile # If options is not given, the default file will be # /etc/sonic/sonic_config.tar. This file will not exist in # older releases, so on upgrade to newer releases, we will need # to fallback to the old `config load` command which will pull # from /etc/sonic/config_db.json. If options is given, then # try to read it as a tarfile, if that fails, then treat is as # a config_db.json dump. rc, output = CFG_MGMT._run_command([load_type, "-y"], options) finally: shutil.rmtree(tempdir, ignore_errors=True) return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def reload(self, options): """Reload configuration and restart services""" return self._load_from_tar('reload', options) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='as', out_signature='is') def load(self, options): """Load configuration""" return self._load_from_tar('load', options) @staticmethod def _get_version(): '''Return the SONiC version string, or NONE if command to retrieve it fails''' try: proc = subprocess.Popen( "sonic-cfggen -y /etc/sonic/sonic_version.yml -v build_version", shell=True, stdout=subprocess.PIPE) out, err = proc.communicate() build_version_info = out.strip() return build_version_info.decode("utf-8") except subprocess.CalledProcessError: return None @staticmethod def _create_host_file(fname, content=None): '''Create a file under /host''' version = CFG_MGMT._get_version() if version: filename = '/host/image-' + version + "/" + fname try: f = open(filename, "w+") if content: f.write(content) f.close() return 0, "" except IOError as e: return 1, ("Unable to create file [%s] - %s" % (filename, e)) else: return 1, "Unable to get SONiC version: operation not performed" @staticmethod def _delete_host_file(fname): '''Delete a file under /host''' version = CFG_MGMT._get_version() if version: filename = '/host/image-' + version + "/" + fname if not os.path.exists(filename): return 1, "No configuration erase operation to cancel." try: os.remove(filename) return 0, "" except IOError as e: return 1, ("Unable to delete file [%s] - %s" % (filename, e)) else: return 1, "Unable to get SONiC version: operation not performed" @staticmethod def _run_command_erase(option): """ Run config mgmt command """ rc = 1 if option == "": rc, err = CFG_MGMT._create_host_file("/pending_erase") elif option == "boot": rc, err = CFG_MGMT._create_host_file("/pending_erase", "boot") elif option == "install": rc, err = CFG_MGMT._create_host_file("/pending_erase", "install") elif option == "no": rc, err = CFG_MGMT._delete_host_file("/pending_erase") return rc, err @host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is') def write_erase(self, option): return self._run_command_erase(option) @staticmethod def _load_config(): """Load configuration for save/load/reload""" config = {} if os.path.exists(CFG_FILE): with open(CFG_FILE, 'r') as cfg: config = json.load(cfg) return config
class Kdump(host_service.HostModule): """DBus endpoint that executes the kdump provided command """ @staticmethod def _run_command(cmd): '''! Execute a given command @param cmd (str) Command to execute. Since we execute the command directly, and not within the context of the shell, the full path needs to be provided ($PATH is not used). Command parameters are simply separated by a space. Should be either string or a list ''' try: shcmd = shlex.split(cmd) proc = subprocess.Popen(shcmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, close_fds=True) output_stdout, output_stderr = proc.communicate() list_stdout = [] for l in output_stdout.splitlines(): list_stdout.append(str(l.decode())) list_stderr = [] for l in output_stderr.splitlines(): list_stderr.append(str(l.decode())) return (proc.returncode, list_stdout, list_stderr) except (OSError, ValueError) as e: print( "!Exception [%s] encountered while processing the command : %s" % (str(e), str(cmd))) return (1, None, None) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='bis', out_signature='is') def command(self, enabled, num_dumps, memory): if memory != '': cmd = '/usr/bin/config kdump memory %s' % memory elif num_dumps != 0: cmd = '/usr/bin/config kdump num_dumps %d' % num_dumps else: if enabled: cmd = '/usr/bin/config kdump enable' else: cmd = '/usr/bin/config kdump disable' (rc, output, output_err) = self._run_command(cmd) result = '' for s in output: if s != '': s = '\n' + s result = result + s return 0, result @host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is') def state(self, param): # All results, formatted as a string if param != None: cmd = '/usr/bin/show kdump %s' % param else: cmd = '/usr/bin/show kdump' (rc, output, output_err) = self._run_command(cmd) result = '' for s in output: if s != '': s = '\n' + s result = result + s return 0, result
class ZTP(host_service.HostModule): """DBus endpoint that executes ZTP related commands """ @staticmethod def _run_command(commands): """Run a ZTP command""" cmd = ['/usr/bin/ztp'] if isinstance(commands, list): cmd.extend(commands) else: cmd.append(commands) try: rc = 0 output = subprocess.check_output(cmd) except subprocess.CalledProcessError as err: rc = err.returncode output = "" return rc, output @staticmethod def _run_command_run(): """Run a ZTP command""" cmd = '/usr/bin/ztp run -y' output = "" try: rc = 0 proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=None) (out, err) = proc.communicate() out = out.decode() rc = proc.returncode if rc == 0: output = out.rstrip('\n') else: if isinstance(out, list): out[0] = out[0].rstrip('\n') rc = 0 output = out except subprocess.CalledProcessError as err: rc = 1 output = out.rstrip('\n') return rc, output @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='') def enable(self): self._run_command("enable") @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='s') def getcfg(self): ztp_cfg = ZTPCfg() ad = getCfg('admin-mode', ztp_cfg=ztp_cfg) if ad == False: return "disabled" else: return "enabled" @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='') def disable(self): self._run_command(["disable", "-y"]) @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='s') def status(self): print("Calling ZTP Status") b = ztp_status() print("Returning", b) return b @host_service.method(host_service.bus_name(MOD_NAME), in_signature='', out_signature='is') def run(self): rc, output = self._run_command_run() return rc, output