Skip to content

Commit 6c28811

Browse files
Merge pull request #269 from amd/alex_mi4xxServiceability
MI4xxServiceabilityPlugin
2 parents 0e23e4f + 24fe1ab commit 6c28811

36 files changed

Lines changed: 3926 additions & 424 deletions

nodescraper/cli/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from .cli import get_cli_top_level_subcommands
2828
from .cli import main as cli_entry
29-
from .embed import CLI_TOP_LEVEL_SUBCOMMANDS, run_cli_return_code, run_main_return_code
29+
from .embed import run_cli_return_code, run_main_return_code
3030
from .invocation import (
3131
PluginRunInvocation,
3232
get_plugin_run_invocation,
@@ -35,7 +35,6 @@
3535
)
3636

3737
__all__ = [
38-
"CLI_TOP_LEVEL_SUBCOMMANDS",
3938
"cli_entry",
4039
"get_cli_top_level_subcommands",
4140
"run_cli_return_code",

nodescraper/cli/cli.py

Lines changed: 162 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737

3838
import nodescraper
3939
from nodescraper.cli.compare_runs import run_compare_runs
40-
from nodescraper.cli.constants import DEFAULT_CONFIG, META_VAR_MAP
40+
from nodescraper.cli.constants import (
41+
DEFAULT_CONFIG,
42+
KEYBOARD_INTERRUPT_EXIT_CODE,
43+
META_VAR_MAP,
44+
)
4145
from nodescraper.cli.dynamicparserbuilder import DynamicParserBuilder
4246
from nodescraper.cli.helper import (
4347
dump_results_to_csv,
@@ -459,6 +463,14 @@ def setup_logger(
459463
return logger
460464

461465

466+
def _handle_keyboard_interrupt(logger: Optional[logging.Logger] = None) -> None:
467+
if logger is not None:
468+
logger.info("Received Ctrl+C. Shutting down...")
469+
else:
470+
sys.stderr.write("Interrupted.\n")
471+
sys.exit(KEYBOARD_INTERRUPT_EXIT_CODE)
472+
473+
462474
def main(
463475
arg_input: Optional[list[str]] = None,
464476
*,
@@ -474,169 +486,175 @@ def main(
474486
plugin_run_result_hooks: Optional callbacks invoked with each plugin's :class:`PluginResult`
475487
after ``run()`` completes (used by embedded hosts such as error-scraper).
476488
"""
477-
if arg_input is None:
478-
arg_input = sys.argv[1:]
479-
480-
plugin_reg = PluginRegistry()
481-
config_reg = _default_config_registry(plugin_reg)
482-
parser, plugin_subparser_map = build_parser(plugin_reg, config_reg)
483-
489+
logger: Optional[logging.Logger] = None
484490
try:
485-
top_level_args, plugin_arg_map, invalid_plugins = process_args(
486-
arg_input, list(plugin_subparser_map.keys())
487-
)
491+
if arg_input is None:
492+
arg_input = sys.argv[1:]
488493

489-
parsed_args = parser.parse_args(top_level_args)
490-
apply_host_cli_args_to_parsed_args(parsed_args, host_cli_args)
491-
merge_plugin_connection_config_from_host_ns(parsed_args, host_cli_args)
492-
system_info = get_system_info(parsed_args)
493-
sname = system_info.name.lower().replace("-", "_").replace(".", "_")
494-
timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
495-
496-
if parsed_args.log_path:
497-
log_path = os.path.join(
498-
parsed_args.log_path,
499-
f"scraper_logs_{sname}_{timestamp}",
500-
)
501-
os.makedirs(log_path)
502-
else:
503-
log_path = None
494+
plugin_reg = PluginRegistry()
495+
config_reg = _default_config_registry(plugin_reg)
496+
parser, plugin_subparser_map = build_parser(plugin_reg, config_reg)
504497

505-
if parsed_args.no_console_log and not log_path:
506-
base_dir = parsed_args.log_path if parsed_args.log_path else "."
507-
log_path = os.path.join(base_dir, f"scraper_logs_{sname}_{timestamp}")
508-
os.makedirs(log_path, exist_ok=True)
509-
510-
logger = setup_logger(
511-
parsed_args.log_level,
512-
log_path,
513-
console=not parsed_args.no_console_log,
514-
)
515-
if log_path:
516-
logger.info("Log path: %s", log_path)
517-
518-
# Log warning if invalid plugin names were provided
519-
if invalid_plugins:
520-
logger.warning(
521-
"Invalid plugin name(s) ignored: %s. Use 'describe plugin' to list available plugins.",
522-
", ".join(invalid_plugins),
498+
try:
499+
top_level_args, plugin_arg_map, invalid_plugins = process_args(
500+
arg_input, list(plugin_subparser_map.keys())
523501
)
524502

525-
if parsed_args.subcmd == "summary":
526-
generate_summary(
527-
parsed_args.search_path,
528-
parsed_args.output_path,
529-
logger,
530-
artifact_dir=log_path,
531-
)
532-
sys.exit(0)
503+
parsed_args = parser.parse_args(top_level_args)
504+
apply_host_cli_args_to_parsed_args(parsed_args, host_cli_args)
505+
merge_plugin_connection_config_from_host_ns(parsed_args, host_cli_args)
506+
system_info = get_system_info(parsed_args)
507+
sname = system_info.name.lower().replace("-", "_").replace(".", "_")
508+
timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
509+
510+
if parsed_args.log_path:
511+
log_path = os.path.join(
512+
parsed_args.log_path,
513+
f"scraper_logs_{sname}_{timestamp}",
514+
)
515+
os.makedirs(log_path)
516+
else:
517+
log_path = None
533518

534-
if parsed_args.subcmd == "describe":
535-
parse_describe(parsed_args, plugin_reg, config_reg, logger)
536-
537-
if parsed_args.subcmd == "compare-runs":
538-
run_compare_runs(
539-
parsed_args.path1,
540-
parsed_args.path2,
541-
plugin_reg,
542-
logger,
543-
skip_plugins=getattr(parsed_args, "skip_plugins", None) or [],
544-
include_plugins=getattr(parsed_args, "include_plugins", None),
545-
truncate_message=not getattr(parsed_args, "dont_truncate", False),
546-
artifact_dir=log_path,
547-
)
548-
sys.exit(0)
519+
if parsed_args.no_console_log and not log_path:
520+
base_dir = parsed_args.log_path if parsed_args.log_path else "."
521+
log_path = os.path.join(base_dir, f"scraper_logs_{sname}_{timestamp}")
522+
os.makedirs(log_path, exist_ok=True)
549523

550-
if parsed_args.subcmd == "show-redfish-oem-allowable":
551-
if not parsed_args.connection_config:
552-
parser.error("show-redfish-oem-allowable requires --connection-config")
553-
raw = parsed_args.connection_config.get("RedfishConnectionManager")
554-
if not raw:
555-
logger.error("Connection config must contain RedfishConnectionManager")
556-
sys.exit(1)
557-
params = RedfishConnectionParams.model_validate(raw)
558-
password = params.password.get_secret_value() if params.password else None
559-
base_url = f"{'https' if params.use_https else 'http'}://{params.host}" + (
560-
f":{params.port}" if params.port else ""
561-
)
562-
conn = RedfishConnection(
563-
base_url=base_url,
564-
username=params.username,
565-
password=password,
566-
timeout=params.timeout_seconds,
567-
use_session_auth=params.use_session_auth,
568-
verify_ssl=params.verify_ssl,
569-
api_root=params.api_root,
524+
logger = setup_logger(
525+
parsed_args.log_level,
526+
log_path,
527+
console=not parsed_args.no_console_log,
570528
)
571-
try:
572-
conn._ensure_session()
573-
allowable = get_oem_diagnostic_allowable_values(conn, parsed_args.log_service_path)
574-
if allowable is None:
575-
logger.warning(
576-
"Could not read OEMDiagnosticDataType@Redfish.AllowableValues from LogService"
577-
)
578-
sys.exit(1)
579-
logger.info("%s", json.dumps(allowable, indent=2))
580-
finally:
581-
conn.close()
582-
sys.exit(0)
529+
if log_path:
530+
logger.info("Log path: %s", log_path)
531+
532+
# Log warning if invalid plugin names were provided
533+
if invalid_plugins:
534+
logger.warning(
535+
"Invalid plugin name(s) ignored: %s. Use 'describe plugin' to list available plugins.",
536+
", ".join(invalid_plugins),
537+
)
538+
539+
if parsed_args.subcmd == "summary":
540+
generate_summary(
541+
parsed_args.search_path,
542+
parsed_args.output_path,
543+
logger,
544+
artifact_dir=log_path,
545+
)
546+
sys.exit(0)
583547

584-
if parsed_args.subcmd == "gen-plugin-config":
548+
if parsed_args.subcmd == "describe":
549+
parse_describe(parsed_args, plugin_reg, config_reg, logger)
550+
551+
if parsed_args.subcmd == "compare-runs":
552+
run_compare_runs(
553+
parsed_args.path1,
554+
parsed_args.path2,
555+
plugin_reg,
556+
logger,
557+
skip_plugins=getattr(parsed_args, "skip_plugins", None) or [],
558+
include_plugins=getattr(parsed_args, "include_plugins", None),
559+
truncate_message=not getattr(parsed_args, "dont_truncate", False),
560+
artifact_dir=log_path,
561+
)
562+
sys.exit(0)
585563

586-
if parsed_args.reference_config_from_logs:
587-
ref_config = generate_reference_config_from_logs(
588-
parsed_args.reference_config_from_logs, plugin_reg, logger
564+
if parsed_args.subcmd == "show-redfish-oem-allowable":
565+
if not parsed_args.connection_config:
566+
parser.error("show-redfish-oem-allowable requires --connection-config")
567+
raw = parsed_args.connection_config.get("RedfishConnectionManager")
568+
if not raw:
569+
logger.error("Connection config must contain RedfishConnectionManager")
570+
sys.exit(1)
571+
params = RedfishConnectionParams.model_validate(raw)
572+
password = params.password.get_secret_value() if params.password else None
573+
base_url = f"{'https' if params.use_https else 'http'}://{params.host}" + (
574+
f":{params.port}" if params.port else ""
575+
)
576+
conn = RedfishConnection(
577+
base_url=base_url,
578+
username=params.username,
579+
password=password,
580+
timeout=params.timeout_seconds,
581+
use_session_auth=params.use_session_auth,
582+
verify_ssl=params.verify_ssl,
583+
api_root=params.api_root,
589584
)
590-
out_dir = log_path if log_path else parsed_args.output_path
591-
path = os.path.join(out_dir, "reference_config.json")
592585
try:
593-
with open(path, "w") as f:
594-
json.dump(
595-
ref_config.model_dump(mode="json", exclude_none=True),
596-
f,
597-
indent=2,
586+
conn._ensure_session()
587+
allowable = get_oem_diagnostic_allowable_values(
588+
conn, parsed_args.log_service_path
589+
)
590+
if allowable is None:
591+
logger.warning(
592+
"Could not read OEMDiagnosticDataType@Redfish.AllowableValues from LogService"
598593
)
599-
logger.info("Reference config written to: %s", path)
600-
except Exception as exp:
601-
logger.error(exp)
594+
sys.exit(1)
595+
logger.info("%s", json.dumps(allowable, indent=2))
596+
finally:
597+
conn.close()
602598
sys.exit(0)
603599

604-
parse_gen_plugin_config(
605-
parsed_args, plugin_reg, config_reg, logger, artifact_dir=log_path
606-
)
600+
if parsed_args.subcmd == "gen-plugin-config":
607601

608-
parsed_plugin_args = {}
609-
for plugin, plugin_args in plugin_arg_map.items():
610-
try:
611-
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
612-
except Exception as e:
613-
logger.error("%s exception parsing args for plugin: %s", str(e), plugin)
602+
if parsed_args.reference_config_from_logs:
603+
ref_config = generate_reference_config_from_logs(
604+
parsed_args.reference_config_from_logs, plugin_reg, logger
605+
)
606+
out_dir = log_path if log_path else parsed_args.output_path
607+
path = os.path.join(out_dir, "reference_config.json")
608+
try:
609+
with open(path, "w") as f:
610+
json.dump(
611+
ref_config.model_dump(mode="json", exclude_none=True),
612+
f,
613+
indent=2,
614+
)
615+
logger.info("Reference config written to: %s", path)
616+
except Exception as exp:
617+
logger.error(exp)
618+
sys.exit(0)
619+
620+
parse_gen_plugin_config(
621+
parsed_args, plugin_reg, config_reg, logger, artifact_dir=log_path
622+
)
614623

615-
if not parsed_plugin_args and not parsed_args.plugin_configs:
616-
logger.info(
617-
"No plugins config args specified, running default config: %s", DEFAULT_CONFIG
624+
parsed_plugin_args = {}
625+
for plugin, plugin_args in plugin_arg_map.items():
626+
try:
627+
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(
628+
plugin_args
629+
)
630+
except Exception as e:
631+
logger.error("%s exception parsing args for plugin: %s", str(e), plugin)
632+
633+
if not parsed_plugin_args and not parsed_args.plugin_configs:
634+
logger.info(
635+
"No plugins config args specified, running default config: %s",
636+
DEFAULT_CONFIG,
637+
)
638+
plugin_configs = [DEFAULT_CONFIG]
639+
else:
640+
plugin_configs = parsed_args.plugin_configs or []
641+
642+
plugin_config_inst_list = get_plugin_configs(
643+
plugin_config_input=plugin_configs,
644+
system_interaction_level=parsed_args.sys_interaction_level,
645+
built_in_configs=config_reg.configs,
646+
parsed_plugin_args=parsed_plugin_args,
647+
plugin_subparser_map=plugin_subparser_map,
618648
)
619-
plugin_configs = [DEFAULT_CONFIG]
620-
else:
621-
plugin_configs = parsed_args.plugin_configs or []
622-
623-
plugin_config_inst_list = get_plugin_configs(
624-
plugin_config_input=plugin_configs,
625-
system_interaction_level=parsed_args.sys_interaction_level,
626-
built_in_configs=config_reg.configs,
627-
parsed_plugin_args=parsed_plugin_args,
628-
plugin_subparser_map=plugin_subparser_map,
629-
)
630649

631-
if parsed_args.skip_sudo:
632-
plugin_config_inst_list[-1].global_args.setdefault("collection_args", {})[
633-
"skip_sudo"
634-
] = True
650+
if parsed_args.skip_sudo:
651+
plugin_config_inst_list[-1].global_args.setdefault("collection_args", {})[
652+
"skip_sudo"
653+
] = True
635654

636-
except Exception as e:
637-
parser.error(str(e))
655+
except Exception as e:
656+
parser.error(str(e))
638657

639-
try:
640658
results = run_plugin_queue_with_invocation(
641659
plugin_reg=plugin_reg,
642660
parsed_args=parsed_args,
@@ -683,8 +701,7 @@ def main(
683701
else:
684702
sys.exit(0)
685703
except KeyboardInterrupt:
686-
logger.info("Received Ctrl+C. Shutting down...")
687-
sys.exit(130)
704+
_handle_keyboard_interrupt(logger)
688705

689706

690707
if __name__ == "__main__":

nodescraper/cli/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
###############################################################################
2626
META_VAR_MAP = {int: "INT", bool: "BOOL", dict: "JSON_STRING", float: "FLOAT", str: "STRING"}
2727
DEFAULT_CONFIG = "NodeStatus"
28+
KEYBOARD_INTERRUPT_EXIT_CODE = 130

nodescraper/cli/embed.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,9 @@
3030
from collections.abc import Callable, Sequence
3131
from typing import Optional
3232

33-
from nodescraper.cli.cli import get_cli_top_level_subcommands
3433
from nodescraper.models.pluginresult import PluginResult
3534

36-
CLI_TOP_LEVEL_SUBCOMMANDS = get_cli_top_level_subcommands()
37-
3835
__all__ = [
39-
"CLI_TOP_LEVEL_SUBCOMMANDS",
40-
"get_cli_top_level_subcommands",
4136
"run_cli_return_code",
4237
"run_main_return_code",
4338
]

0 commit comments

Comments
 (0)