From b4fd76ff9a419e47c668865be589cb65710d5e8f Mon Sep 17 00:00:00 2001 From: Dalton Bohning Date: Wed, 17 Jan 2024 21:21:28 +0000 Subject: [PATCH 1/2] DAOS-15031 test: launch.py: exit early if extra yaml is not a file Test-tag: test_always_passes test_soak_smoke Skip-unit-tests: true Skip-fault-injection-test: true Exit as early as possible if extra yaml is not a file. Also convert servers and clients to a NodeSet early. Also parse --logs_threshold early Required-githooks: true Signed-off-by: Dalton Bohning --- src/tests/ftest/launch.py | 121 +++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/src/tests/ftest/launch.py b/src/tests/ftest/launch.py index dc5f9f84247..2509c806332 100755 --- a/src/tests/ftest/launch.py +++ b/src/tests/ftest/launch.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ - (C) Copyright 2018-2023 Intel Corporation. + (C) Copyright 2018-2024 Intel Corporation. SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -9,7 +9,7 @@ import logging import os import sys -from argparse import ArgumentParser, RawDescriptionHelpFormatter +from argparse import ArgumentParser, ArgumentTypeError, RawDescriptionHelpFormatter from collections import OrderedDict from tempfile import TemporaryDirectory @@ -253,32 +253,15 @@ def _run(self, args): for key in sorted(args.__dict__.keys()): logger.debug(" %s = %s", key, getattr(args, key)) - # Convert host specifications into NodeSets - try: - test_servers = NodeSet(args.test_servers) - except TypeError: - message = f"Invalid '--test_servers={args.test_servers}' argument" - return self.get_exit_status(1, message, "Setup", sys.exc_info()) - try: - test_clients = NodeSet(args.test_clients) - except TypeError: - message = f"Invalid '--test_clients={args.test_clients}' argument" - return self.get_exit_status(1, message, "Setup", sys.exc_info()) - try: - control_host = NodeSet(args.slurm_control_node) - except TypeError: - message = f"Invalid '--slurm_control_node={args.slurm_control_node}' argument" - return self.get_exit_status(1, message, "Setup", sys.exc_info()) - # A list of server hosts is required - if not test_servers and not args.list: + if not args.test_servers and not args.list: return self.get_exit_status(1, "Missing required '--test_servers' argument", "Setup") - logger.info("Testing with hosts: %s", test_servers.union(test_clients)) - self.details["test hosts"] = str(test_servers.union(test_clients)) + logger.info("Testing with hosts: %s", args.test_servers.union(args.test_clients)) + self.details["test hosts"] = str(args.test_servers.union(args.test_clients)) # Add the installed packages to the details json # pylint: disable=unsupported-binary-operation - all_hosts = test_servers | test_clients | self.local_host + all_hosts = args.test_servers | args.test_clients | self.local_host self.details["installed packages"] = find_packages( logger, all_hosts, "'^(daos|libfabric|mercury|ior|openmpi|mpifileutils)-'") @@ -289,8 +272,8 @@ def _run(self, args): set_test_environment(logger) else: set_test_environment( - logger, test_env, test_servers, test_clients, args.provider, args.insecure_mode, - self.details) + logger, test_env, args.test_servers, args.test_clients, args.provider, + args.insecure_mode, self.details) except TestEnvironmentException as error: message = f"Error setting up test environment: {str(error)}" return self.get_exit_status(1, message, "Setup", sys.exc_info()) @@ -310,8 +293,8 @@ def _run(self, args): # Define the test configs specified by the arguments group = TestGroup( - self.avocado, test_env, test_servers, test_clients, control_host, args.tags, args.nvme, - yaml_dir, args.yaml_extension) + self.avocado, test_env, args.test_servers, args.test_clients, args.slurm_control_node, + args.tags, args.nvme, yaml_dir, args.yaml_extension) try: group.list_tests(logger, args.verbose) except RunException: @@ -329,7 +312,7 @@ def _run(self, args): # Setup the fuse configuration try: - setup_fuse_config(logger, test_servers | test_clients) + setup_fuse_config(logger, args.test_servers | args.test_clients) except LaunchException: # Warn but don't fail message = "Issue detected setting up the fuse configuration" @@ -339,7 +322,7 @@ def _run(self, args): core_files = {} if args.process_cores: try: - all_hosts = test_servers | test_clients | self.local_host + all_hosts = args.test_servers | args.test_clients | self.local_host core_files = get_core_file_pattern(logger, all_hosts) except LaunchException: message = "Error obtaining the core file pattern information" @@ -350,7 +333,7 @@ def _run(self, args): # Determine if bullseye code coverage collection is enabled code_coverage = CodeCoverage(test_env) # pylint: disable=unsupported-binary-operation - code_coverage.check(logger, test_servers | self.local_host) + code_coverage.check(logger, args.test_servers | self.local_host) # Update the test yaml files for the tests in this test group try: @@ -396,6 +379,73 @@ def _run(self, args): return self.get_exit_status(status, "Executing tests complete") +def __arg_type_file(val): + """Parse a file argument. + + Args: + val (str): path to a file + + Returns: + str: the file path + + Raises: + ArgumentTypeError: if val is not a file + """ + if not os.path.isfile(val): + raise ArgumentTypeError(f'File not found: {val}') + return val + + +def __arg_type_nodeset(val): + """Parse a NodeSet argument. + + Args: + val (str): string representation of a NodeSet to parse + + Returns: + NodeSet: the NodeSet + + Raises: + ArgumentTypeError: if val cannot be parsed as a NodeSet + """ + try: + return NodeSet(val) + except Exception as err: # pylint: disable=broad-except + raise ArgumentTypeError(f'Invalid NodeSet: {val}') from err + + +def __arg_type_num_bytes(val): + """Parse a bytes argument, optionally with B, K, M, G, T suffix. + + Args: + val (str): string representation of bytes to parse + + Returns: + str: the bytes normalized as an integer + + Raises: + ArgumentTypeError: if val cannot be parsed as a number of bytes + """ + suffixes = { + 'KB': 1024, + 'K': 1024, + 'MB': 1024**2, + 'M': 1024**2, + 'GB': 1024**3, + 'G': 1024**3, + 'TB': 1024**4, + 'T': 1024**4, + 'B': 1, # Check B last in case e.g. KB is used + } + try: + for suffix, multiplier in suffixes.items(): + if val.endswith(suffix): + return str(int(val.rstrip(suffix)) * multiplier) + return str(int(val)) + except Exception as err: # pylint: disable=broad-except + raise ArgumentTypeError(f'Invalid number of bytes: {val}') from err + + def main(): """Launch DAOS functional tests.""" # Parse the command line arguments @@ -473,7 +523,7 @@ def main(): "-e", "--extra_yaml", action="append", default=None, - type=str, + type=__arg_type_file, help="additional yaml file to include with the test yaml file. Any " "entries in the extra yaml file can be used to replace an " "existing entry in the test yaml file.") @@ -560,8 +610,8 @@ def main(): parser.add_argument( "-sc", "--slurm_control_node", action="store", - default=str(get_local_host()), - type=str, + type=__arg_type_nodeset, + default=get_local_host(), help="slurm control node where scontrol commands will be issued to check for the existence " "of any slurm partitions required by the tests") parser.add_argument( @@ -596,11 +646,14 @@ def main(): parser.add_argument( "-tc", "--test_clients", action="store", + type=__arg_type_nodeset, + default=NodeSet(), help="comma-separated list of hosts to use as replacement values for " "client placeholders in each test's yaml file") parser.add_argument( "-th", "--logs_threshold", action="store", + type=__arg_type_num_bytes, help="collect log sizes and report log sizes that go past provided" "threshold. e.g. '-th 5M'" "Valid threshold units are: B, K, M, G, T") @@ -613,6 +666,8 @@ def main(): parser.add_argument( "-ts", "--test_servers", action="store", + type=__arg_type_nodeset, + default=NodeSet(), help="comma-separated list of hosts to use as replacement values for " "server placeholders in each test's yaml file. If the " "'--test_clients' argument is not specified, this list of hosts " From 825fd29daa251d7d1aa9fd33a85d3ae74ecf3747 Mon Sep 17 00:00:00 2001 From: Dalton Bohning Date: Thu, 18 Jan 2024 18:40:28 +0000 Subject: [PATCH 2/2] simplify -th arg Test-tag: pr,vm test_soak_smoke Skip-unit-tests: true Skip-fault-injection-test: true Based on find -size https://man7.org/linux/man-pages/man1/find.1.html Required-githooks: true Signed-off-by: Dalton Bohning --- src/tests/ftest/launch.py | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/tests/ftest/launch.py b/src/tests/ftest/launch.py index 2509c806332..7b92bc0cfb5 100755 --- a/src/tests/ftest/launch.py +++ b/src/tests/ftest/launch.py @@ -8,6 +8,7 @@ import json import logging import os +import re import sys from argparse import ArgumentParser, ArgumentTypeError, RawDescriptionHelpFormatter from collections import OrderedDict @@ -414,36 +415,21 @@ def __arg_type_nodeset(val): raise ArgumentTypeError(f'Invalid NodeSet: {val}') from err -def __arg_type_num_bytes(val): - """Parse a bytes argument, optionally with B, K, M, G, T suffix. +def __arg_type_find_size(val): + """Parse a find -size argument. Args: - val (str): string representation of bytes to parse + val (str): string representation of find -size argument Returns: - str: the bytes normalized as an integer + str: the find -size argument Raises: - ArgumentTypeError: if val cannot be parsed as a number of bytes + ArgumentTypeError: if val cannot be parsed as a find -size argument """ - suffixes = { - 'KB': 1024, - 'K': 1024, - 'MB': 1024**2, - 'M': 1024**2, - 'GB': 1024**3, - 'G': 1024**3, - 'TB': 1024**4, - 'T': 1024**4, - 'B': 1, # Check B last in case e.g. KB is used - } - try: - for suffix, multiplier in suffixes.items(): - if val.endswith(suffix): - return str(int(val.rstrip(suffix)) * multiplier) - return str(int(val)) - except Exception as err: # pylint: disable=broad-except - raise ArgumentTypeError(f'Invalid number of bytes: {val}') from err + if not re.match(r'^[0-9]+[bcwkMG]?$', val): + raise ArgumentTypeError(f'Invalid find -size argument: {val}') + return val def main(): @@ -653,10 +639,10 @@ def main(): parser.add_argument( "-th", "--logs_threshold", action="store", - type=__arg_type_num_bytes, + type=__arg_type_find_size, help="collect log sizes and report log sizes that go past provided" "threshold. e.g. '-th 5M'" - "Valid threshold units are: B, K, M, G, T") + "Valid threshold units are: b, c, w, k, M, G for find -size") parser.add_argument( "-tm", "--timeout_multiplier", action="store",