diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/system_option.py | 99 | ||||
| -rw-r--r-- | src/etc/systemd/system/kdump-tools.service.d/override.conf | 19 | ||||
| -rwxr-xr-x | src/helpers/run-config-activation.py | 3 | ||||
| -rwxr-xr-x | src/helpers/run-config-migration.py | 5 | ||||
| -rwxr-xr-x | src/helpers/vyos-boot-config-loader.py | 7 | ||||
| -rwxr-xr-x | src/op_mode/generate_tech-support_archive.py | 28 | ||||
| -rwxr-xr-x | src/op_mode/image_installer.py | 18 | ||||
| -rw-r--r-- | src/op_mode/kdump.py | 256 | ||||
| -rwxr-xr-x | src/op_mode/memory.py | 19 | ||||
| -rw-r--r-- | src/op_mode/show_techsupport_report.py | 5 |
10 files changed, 432 insertions, 27 deletions
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py index 8951adcff..190db232d 100755 --- a/src/conf_mode/system_option.py +++ b/src/conf_mode/system_option.py @@ -26,22 +26,27 @@ from vyos.base import Warning from vyos.config import Config from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists +from vyos.defaults import KDUMP_DEFAULT_MEMORY_AUTO +from vyos.system import image from vyos.system import grub_util from vyos.template import render from vyos.utils.boot import boot_configuration_complete from vyos.utils.convert import range_str_to_list from vyos.utils.convert import list_to_range_str +from vyos.utils.convert import human_to_bytes from vyos.utils.cpu import get_cpus from vyos.utils.cpu import get_available_cpus from vyos.utils.dict import dict_search from vyos.utils.file import write_file -from vyos.utils.file import read_file from vyos.utils.kernel import check_kmod +from vyos.utils.kernel import get_kernel_boot_arg +from vyos.utils.kernel import get_crash_kernel_size from vyos.utils.process import cmdl from vyos.utils.process import is_systemd_service_running from vyos.utils.network import is_addr_assigned from vyos.utils.network import is_intf_addr_assigned from vyos.utils.system import sysctl_write +from vyos.utils.memory import get_memory_info from vyos.configdep import set_dependents from vyos.configdep import call_dependents from vyos import ConfigError @@ -73,6 +78,15 @@ tuned_profiles = { 'virtual-host': 'virtual-host', } +# Path to the kdump-tools configuration file controlling dump behaviour +KDUMP_CONFIG_FILE = '/etc/default/kdump-tools' + +# initramfs-tools drop-in configuration applied during kdump initrd +# generation. Named with 'zzzz-' prefix to ensure it is sourced last, +# overriding any conflicting settings from other drop-ins (including the +# `MODULES=dep` override written by the kdump-tools kernel postinst hook). +KDUMP_INITRAMFS_HOOK = '/etc/initramfs-tools/conf.d/zzzz-kdump-vyos-overlay' + MANAGED_PARAMS = { 'hugepages1g': { 'parse': r'hugepagesz=1[Gg]\s+hugepages=(?P<hugepages1g>\d+)', @@ -168,6 +182,11 @@ MANAGED_PARAMS = { 'clean': r'numa_balancing=\S+', 'type': str, }, + 'crashkernel': { + 'parse': r'crashkernel=(?P<crashkernel>\S+)', + 'clean': r'crashkernel=\S+', + 'type': str, + }, } # Compiled regex pattern for parsing command line options @@ -198,9 +217,18 @@ def get_config(config=None): conf = Config() base = ['system', 'option'] options = conf.get_config_dict( - base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True + base, + key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True, + with_pki=True, ) + if 'kdump' not in options: + options['kdump'] = {} + options['kdump']['is_live_boot'] = image.is_live_boot() + options['kdump']['disabled'] = not conf.exists(base + ['kdump']) + if 'performance' in options: # Update IPv4/IPv6 and sysctl options after tuned applied it's settings set_dependents('ip_ipv6', conf) @@ -314,6 +342,32 @@ def verify(options): f'({reserved_gb} GB is reserved for system usage and services)' ) + if not options['kdump']['disabled']: + kdump = options['kdump'] + + if kdump.get('is_live_boot'): + raise ConfigError('kdump is not supported on live boot images') + + # Check that the configured crash kernel memory does not exceed the + # total system RAM for the applicable range + memory = kdump.get('memory') + if memory and memory != 'auto': + memory_info = get_memory_info() + # Convert kilobytes to bytes + memory_total = memory_info['MemTotal'] * 1024 + + # 'memory_info' doesn't consider crash kernel reservation size + memory_total += get_crash_kernel_size() + + # Convert scalar value from megabytes to bytes + memory_bytes = human_to_bytes(f'{memory}mb') + + # Ensure that specified memory does not exceed total virtual memory + if memory_bytes >= memory_total: + raise ConfigError( + f'Specified kdump memory {memory!r} exceeds the available system RAM' + ) + return None @@ -401,6 +455,45 @@ def generate(options): if count: cmdline_options.append(f'hugepages={count}') + kdump = options['kdump'] + if not kdump['disabled']: + # Crash kernel memory reservation + kdump_memory = kdump.get('memory') + if kdump_memory: + if kdump_memory == 'auto': + # Tiered auto-sizing based on total system RAM + kdump_memory = KDUMP_DEFAULT_MEMORY_AUTO + else: + # Append suffix (megabytes) to right format + kdump_memory = f'{kdump_memory}M' + + # The `crashkernel=` parameter instructs the boot kernel to reserve a + # dedicated memory region for the capture kernel. + cmdline_options.append(f'crashkernel={kdump_memory}') + + # Render the kdump-tools configuration file from the Jinja2 template + render(KDUMP_CONFIG_FILE, 'system/kdump_tools.conf.j2', kdump) + + # VyOS uses an OverlayFS root filesystem. When mkinitramfs runs with + # `MODULES=dep`, it attempts to determine which kernel modules are needed + # by walking up the device tree from /. On a real block device + # this works correctly, but OverlayFS has no backing block device to walk from, + # causing mkinitramfs to fail with: + # mkinitramfs: failed to determine device for / + write_file(KDUMP_INITRAMFS_HOOK, 'MODULES=most') + else: + # kdump has been disabled - render the + # kdump-tools configuration with the disabled flag so the service + # starts in a no-op state rather than attempting to load a capture + # kernel with stale or missing parameters. + render(KDUMP_CONFIG_FILE, 'system/kdump_tools.conf.j2', kdump) + + # Remove ephemeral files after the feature is disabled + kdump_delete_files = (KDUMP_INITRAMFS_HOOK,) + for delete_file in kdump_delete_files: + if os.path.exists(delete_file): + os.unlink(delete_file) + cmdline_options_str = ' '.join(cmdline_options) grub_util.update_kernel_cmdline_options(cmdline_options_str) @@ -464,7 +557,7 @@ def generate_cmdline_for_kexec(options): - new_cmdline (str): The updated kernel command line string. """ # Read current cmdline and parse it - current_cmdline = read_file('/proc/cmdline').strip() + current_cmdline = get_kernel_boot_arg().strip() current_parsed = parse_cmdline(current_cmdline) # Parse desired options from options['cmdline_options'] diff --git a/src/etc/systemd/system/kdump-tools.service.d/override.conf b/src/etc/systemd/system/kdump-tools.service.d/override.conf new file mode 100644 index 000000000..67419e10b --- /dev/null +++ b/src/etc/systemd/system/kdump-tools.service.d/override.conf @@ -0,0 +1,19 @@ +[Unit] +Description=Kernel crash dump capture service +DefaultDependencies=no +Before= +After= + +[Service] +Type=oneshot +StandardOutput=journal+console +EnvironmentFile=/etc/default/kdump-tools +ExecStart= +ExecStart=/etc/init.d/kdump-tools start +ExecStop= +ExecStop=/etc/init.d/kdump-tools stop +RemainAfterExit=yes + +[Install] +WantedBy= +WantedBy=vyos.target diff --git a/src/helpers/run-config-activation.py b/src/helpers/run-config-activation.py index 7e7a6571e..9e048e193 100755 --- a/src/helpers/run-config-activation.py +++ b/src/helpers/run-config-activation.py @@ -30,6 +30,7 @@ from vyos.utils.activate import set_activation from vyos.utils.activate import is_active from vyos.utils.system import load_as_module from vyos.utils.func import FalseCallable +from vyos.utils.kernel import get_kernel_boot_arg from vyos.defaults import directories from vyos.defaults import activation_list @@ -54,7 +55,7 @@ fh.setFormatter(formatter) logger.addHandler(fh) -if 'vyos-activate-debug' in Path('/proc/cmdline').read_text(): +if get_kernel_boot_arg('vyos-activate-debug') is not None: print(f'\nactivate-debug enabled: file {checkpoint_file}_* on error') debug = checkpoint_file logger.setLevel(logging.DEBUG) diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py index 6329d2979..f538c1e86 100755 --- a/src/helpers/run-config-migration.py +++ b/src/helpers/run-config-migration.py @@ -19,7 +19,7 @@ import sys import time from argparse import ArgumentParser from shutil import copyfile -from vyos.utils.file import read_file +from vyos.utils.kernel import get_kernel_boot_arg from vyos.migrate import ConfigMigrate from vyos.migrate import ConfigMigrateError @@ -80,6 +80,5 @@ if backup is not None and not config_migrate.config_modified: # T1771: add knob on Kernel command-line to simulate failed config migrator run # used to test if the automatic image reboot works. -kernel_cmdline = read_file('/proc/cmdline') -if 'vyos-fail-migration' in kernel_cmdline.split(): +if get_kernel_boot_arg('vyos-fail-migration') is not None: sys.exit(1) diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py index a3a66eedc..e01b5bff6 100755 --- a/src/helpers/vyos-boot-config-loader.py +++ b/src/helpers/vyos-boot-config-loader.py @@ -28,6 +28,7 @@ from vyos.configsession import ConfigSessionError from vyos.configtree import ConfigTree from vyos.utils.process import cmdl from vyos.utils.file import write_file +from vyos.utils.kernel import get_kernel_boot_arg STATUS_FILE = config_status TRACE_FILE = '/tmp/boot-config-trace' @@ -44,11 +45,9 @@ else: LOG_FILE = LOG_DIR + '/vyos-boot-config-loader.log' try: - with open('/proc/cmdline', 'r') as f: - cmdline = f.read() - if 'vyos-debug' in cmdline: + if get_kernel_boot_arg('vyos-debug') is not None: os.environ['VYOS_DEBUG'] = 'yes' - if 'vyos-config-debug' in cmdline: + if get_kernel_boot_arg('vyos-config-debug') is not None: os.environ['VYOS_DEBUG'] = 'yes' trace_config = True except Exception as e: diff --git a/src/op_mode/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py index 2719d231c..7b539473c 100755 --- a/src/op_mode/generate_tech-support_archive.py +++ b/src/op_mode/generate_tech-support_archive.py @@ -17,6 +17,7 @@ import os import argparse import glob +import json from datetime import datetime from pathlib import Path from shutil import rmtree @@ -24,7 +25,9 @@ from socket import gethostname from sys import exit from tarfile import open as tar_open +from vyos.base import Warning from vyos.defaults import directories +from vyos.utils.process import rc_cmd from vyos.utils.process import call from vyos.utils.process import cmdl from vyos.utils.file import get_name_from_path @@ -38,6 +41,8 @@ ARCHIVE_TMP_DIR_PATTERN = 'drops-debug_' DEFAULT_TMP_DIR = '/tmp' EXCLUDED_ARCHIVE_EXT = ('.iso', '.gz', '.tar', '.zip') +vyos_op_scripts_dir = directories['op_mode'] + def __rotate_logs(path: str, log_pattern:str): files_list = glob.glob(f'{path}/{log_pattern}') @@ -53,7 +58,6 @@ def __save_show_report_files(reports_dir: Path): :type reports_dir: pathlib.Path """ - vyos_op_scripts_dir = directories['op_mode'] script_path = f'{vyos_op_scripts_dir}/show_techsupport_report.py' arguments = [ '--launched-from-generate-archive', @@ -111,6 +115,28 @@ def __generate_archived_files(location_path: str) -> None: 'run': '/run', } + # Get the directory of the last crash dump and add the crash directory to archive dictionary + err_code, out = rc_cmd([f'{vyos_op_scripts_dir}/kdump.py', 'show_dumps', '--raw']) + if not err_code: + try: + dumps = json.loads(out) + except json.decoder.JSONDecodeError as e: + Warning(f'Unable to parse kdump dump list. {e}') + dumps = [] + + if dumps: + last_dump = dumps[-1] + # Before enabling the dump, consider the size as + # it can range from hundreds of MB to several GB + max_dump_archive_bytes = 1 * 1024**3 # 1 GB cap + if last_dump['size_bytes'] > max_dump_archive_bytes: + size_human = last_dump['size_human'] + Warning( + f"Skipping crash dump archival: {size_human} exceeds limit 1 GB" + ) + else: + archive_dict['last-crash-dump'] = last_dump['directory'] + for archive_name, path in archive_dict.items(): if not os.path.exists(path): continue diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index 5174ff3d2..fb28f182e 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -47,6 +47,7 @@ from vyos.config_mgmt import unsaved_commits from vyos.defaults import base_dir from vyos.defaults import directories from vyos.defaults import activation_hint +from vyos.defaults import KDUMP_DEFAULT_MEMORY_AUTO from vyos.flavor import get_image_serial_console from vyos.remote import download from vyos.system import disk @@ -525,9 +526,11 @@ def get_cli_kernel_options(config_file: str) -> list: config = ConfigTree(read_file(config_file)) config_dict = loads(config.to_json()) cmdline_options = [] - kernel_options = dict_search('system.option.kernel', config_dict) - if kernel_options is None: + + options = dict_search('system.option', config_dict) + if options is None: return cmdline_options + kernel_options = options.get('kernel', {}) k_cpu_opts = kernel_options.get('cpu', {}) k_memory_opts = kernel_options.get('memory', {}) @@ -591,6 +594,17 @@ def get_cli_kernel_options(config_file: str) -> list: if count: cmdline_options.append(f'hugepages={count}') + # Crash kernel memory reservation for the capture kernel + kdump_memory = options.get('kdump', {}).get('memory') + if kdump_memory: + if kdump_memory == 'auto': + # Tiered auto-sizing based on total system RAM + kdump_memory = KDUMP_DEFAULT_MEMORY_AUTO + else: + # Append suffix (megabytes) to right format + kdump_memory = f'{kdump_memory}M' + cmdline_options.append(f'crashkernel={kdump_memory}') + return cmdline_options def configure_authentication(config_file: str, password: str) -> None: diff --git a/src/op_mode/kdump.py b/src/op_mode/kdump.py new file mode 100644 index 000000000..560650bdf --- /dev/null +++ b/src/op_mode/kdump.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +from datetime import datetime + +import vyos.opmode +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.defaults import systemd_services +from vyos.utils.convert import bytes_to_human +from vyos.utils.kernel import get_kernel_boot_arg +from vyos.utils.kernel import is_crash_kernel_loaded +from vyos.utils.kernel import get_crash_kernel_size +from vyos.utils.process import is_systemd_service_active + +# systemd unit managed by kdump-tools that loads the capture kernel +# and handles vmcore saving after a panic +KDUMP_SERVICE = systemd_services['kdump'] + +# Base path to boot files related to kdump +KDUMP_LIB_PATH = '/var/lib/kdump' + +DEFAULT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' + +conf = ConfigTreeQuery() +base = ['system', 'option', 'kdump'] + + +def _get_config() -> dict: + """Return the effective kdump config dict, or {} when unconfigured""" + + if not conf.exists(base): + return {} + + return conf.get_config_dict( + base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True + ) + + +def _read_link(symlink_path: str) -> str | None: + """Read the destination of the symbolic link""" + try: + destination_path = os.readlink(symlink_path) + except OSError: + return None + + # os.readlink() may return a relative target. + # Interpret it relative to the link directory + if not os.path.isabs(destination_path): + destination_path = os.path.join(os.path.dirname(symlink_path), destination_path) + + destination_path = os.path.realpath(destination_path) + return destination_path if os.path.isfile(destination_path) else None + + +def _list_dumps(dump_path: str) -> list[dict]: + """Return a sorted list of vmcore entries found under *dump_path*. + + kdump-tools stores each capture in its own timestamped sub-directory + using the layout: + + <dump_path>/<YYYYMMDDHHMM>/dump.<YYYYMMDDHHMM> + <dump_path>/<YYYYMMDDHHMM>/dmesg.<YYYYMMDDHHMM> (optional) + + The reported size is the combined size of both files when the dmesg + companion file is present. + """ + + entries = [] + if not os.path.isdir(dump_path): + return entries + + # Each sub-directory name is the timestamp of the crash event + for name in sorted(os.listdir(dump_path)): + sub = os.path.join(dump_path, name) + if not os.path.isdir(sub): + continue + + dump = os.path.join(sub, f'dump.{name}') + dmesg = os.path.join(sub, f'dmesg.{name}') + + if os.path.isfile(dump): + stat_dump = os.stat(dump) + stat_dmesg = os.stat(dmesg) if os.path.isfile(dmesg) else None + full_size = stat_dump.st_size + (stat_dmesg.st_size if stat_dmesg else 0) + + entry = { + 'directory': sub, + 'size_bytes': full_size, + 'size_human': bytes_to_human(full_size), + 'modify_time': stat_dump.st_mtime, + } + + entries.append(entry) + + return entries + + +def _get_raw_status() -> dict: + """Collect all kdump runtime and configuration data into a single dict""" + + cfg = _get_config() + + memory = cfg.get('memory') + dump_path = cfg.get('dump_path') + dumps = _list_dumps(dump_path) if dump_path else [] + last_dump = dumps[-1] if dumps else None + + return { + 'configured': bool(cfg), + 'service_active': is_systemd_service_active(KDUMP_SERVICE), + 'crash_kernel_loaded': is_crash_kernel_loaded(), + 'crash_kernel_size': get_crash_kernel_size(), + 'cmdline_value': get_kernel_boot_arg('crashkernel') or '', + 'vmlinuz_path': _read_link(f'{KDUMP_LIB_PATH}/vmlinuz'), + 'initrd_path': _read_link(f'{KDUMP_LIB_PATH}/initrd.img'), + 'memory': memory, + 'dump_path': dump_path, + 'last_dump': last_dump, + } + + +def _get_raw_dumps() -> list[dict]: + cfg = _get_config() + dump_path = cfg.get('dump_path') + return _list_dumps(dump_path) if dump_path else [] + + +def _verify_config(): + """Raise UnconfiguredSubsystem when kdump has not been configured""" + + cfg = _get_config() + if not cfg: + conf_command = ' '.join(['set'] + base) + raise vyos.opmode.UnconfiguredSubsystem( + f'kdump is not configured - use "{conf_command}" to enable it.' + ) + + +def _format_status(data: dict) -> str: + """Render the kdump status dict as a human-readable plain-text table""" + + header = 'Kernel crash dump (kdump) status' + + cfg_str = 'configured' if data['configured'] else 'not configured' + svc_str = 'active' if data['service_active'] else 'inactive' + loaded_str = 'loaded' if data['crash_kernel_loaded'] else 'not loaded' + + rows = [ + ('Configuration', cfg_str), + ('Systemd service', svc_str), + ('Crash kernel', loaded_str), + ] + + if data['crash_kernel_loaded']: + # Show reserved memory size and the boot argument that produced it + + raw = data['crash_kernel_size'] + rows.append(('Reserved memory', bytes_to_human(raw))) + + rows.append(("Boot 'crashkernel' value", data['cmdline_value'])) + else: + # Capture kernel is not loaded and memory reservation requires a reboot + rows.append(('Reserved memory', 'none (reboot required to reserve memory)')) + + if data['configured']: + rows.append(('Memory parameter', data['memory'])) + rows.append(('Directory to save dumps', data['dump_path'])) + + # Display the latest date of created dump + last_dump_date = 'none' + if data['last_dump']: + dt = datetime.fromtimestamp(data['last_dump']['modify_time']) + last_dump_date = dt.strftime(DEFAULT_TIME_FORMAT) + rows.append(('Last dump date', last_dump_date)) + + # Show `initrd.img` and `vmlinuz` status which are critical + # for a successful boot process + rows.append(('Initial RAM disk image', data['initrd_path'] or 'none')) + rows.append(('Linux kernel executable file', data['vmlinuz_path'] or 'none')) + + # Expand each row to a three-column tuple so tabulate can align the + # separator colon independently of the label and value columns + rows = [(header, ':', value) for header, value in rows] + table = tabulate(rows, tablefmt='plain') + + return f'{header}\n\n{table}' + + +def _format_dumps(entries: list[dict]) -> str: + """Render the crash dump list as a human-readable table""" + + if not entries: + return 'No kernel crash dumps recorded' + + headers = ['DIRECTORY', 'SIZE', 'TIME'] + rows = [] + + for entry in entries: + dt = datetime.fromtimestamp(entry['modify_time']) + row = ( + entry['directory'], + entry['size_human'], + dt.strftime(DEFAULT_TIME_FORMAT), + ) + rows.append(row) + + # Append a blank separator row followed by an aggregate totals row + total = sum(entry['size_bytes'] for entry in entries) + rows.append(('', '', '')) + rows.append((f'Total: {len(entries)} dump(s)', bytes_to_human(total), '')) + + return tabulate(rows, headers, tablefmt='simple') + + +def show_status(raw: bool): + """Show kdump service status and configuration summary""" + + _verify_config() + + data = _get_raw_status() + return data if raw else _format_status(data) + + +def show_dumps(raw: bool): + """Show recorded kernel crash dumps""" + + _verify_config() + + data = _get_raw_dumps() + return data if raw else _format_dumps(data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py index 20d937243..47a9c476e 100755 --- a/src/op_mode/memory.py +++ b/src/op_mode/memory.py @@ -18,23 +18,16 @@ import sys import vyos.opmode +from vyos.utils.memory import get_memory_info def _get_raw_data(): - from re import search as re_search + mem_info = get_memory_info() - def find_value(keyword, mem_data): - regex = keyword + ':\s+(\d+)' - res = re_search(regex, mem_data).group(1) - return int(res) - - with open("/proc/meminfo", "r") as f: - mem_data = f.read() - - total = find_value('MemTotal', mem_data) - available = find_value('MemAvailable', mem_data) - buffers = find_value('Buffers', mem_data) - cached = find_value('Cached', mem_data) + total = mem_info['MemTotal'] + available = mem_info['MemAvailable'] + buffers = mem_info['Buffers'] + cached = mem_info['Cached'] used = total - available diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py index 2434da29a..fa6bff6ba 100644 --- a/src/op_mode/show_techsupport_report.py +++ b/src/op_mode/show_techsupport_report.py @@ -543,6 +543,11 @@ REPORTS: dict[str, tuple[BaseSpec]] = { CommandSpec('', 'vppctl show mpls tunnel'), CommandSpec('', 'vppctl show trace'), ), + 'kernel-dump': ( + CommandSpec('Kdump status and configuration', op('show system kdump')), + CommandSpec('', 'kdump-config show'), + CommandSpec('Recorded kernel crash dumps', op('show system kdump dumps')), + ), } |
