import logging import re from frostfs_testlib.cli import FrostfsAdm, FrostfsCli from frostfs_testlib.hosting import Host, Hosting from frostfs_testlib.resources.cli import FROSTFS_ADM_EXEC, FROSTFS_AUTHMATE_EXEC, FROSTFS_CLI_EXEC, NEOGO_EXECUTABLE from frostfs_testlib.resources.common import DEFAULT_WALLET_CONFIG from frostfs_testlib.shell import Shell from frostfs_testlib.testing.parallel import parallel logger = logging.getLogger("NeoLogger") def get_local_binaries_versions(shell: Shell) -> dict[str, str]: versions = {} for binary in [NEOGO_EXECUTABLE, FROSTFS_AUTHMATE_EXEC]: out = shell.exec(f"{binary} --version").stdout versions[binary] = _parse_version(out) frostfs_cli = FrostfsCli(shell, FROSTFS_CLI_EXEC, DEFAULT_WALLET_CONFIG) versions[FROSTFS_CLI_EXEC] = _parse_version(frostfs_cli.version.get().stdout) try: frostfs_adm = FrostfsAdm(shell, FROSTFS_ADM_EXEC) versions[FROSTFS_ADM_EXEC] = _parse_version(frostfs_adm.version.get().stdout) except RuntimeError: logger.info(f"{FROSTFS_ADM_EXEC} not installed") out = shell.exec("aws --version").stdout out_lines = out.split("\n") versions["AWS"] = out_lines[0] if out_lines else "Unknown" return versions def parallel_binary_verions(host: Host) -> dict[str, str]: versions_by_host = {} binary_path_by_name = {} # Maps binary name to executable path for service_config in host.config.services: exec_path = service_config.attributes.get("exec_path") requires_check = service_config.attributes.get("requires_version_check", "true") if exec_path: binary_path_by_name[service_config.name] = { "exec_path": exec_path, "check": requires_check.lower() == "true", } for cli_config in host.config.clis: requires_check = cli_config.attributes.get("requires_version_check", "true") binary_path_by_name[cli_config.name] = { "exec_path": cli_config.exec_path, "check": requires_check.lower() == "true", } shell = host.get_shell() versions_at_host = {} for binary_name, binary in binary_path_by_name.items(): try: binary_path = binary["exec_path"] result = shell.exec(f"{binary_path} --version") versions_at_host[binary_name] = {"version": _parse_version(result.stdout), "check": binary["check"]} except Exception as exc: logger.error(f"Cannot get version for {binary_path} because of\n{exc}") versions_at_host[binary_name] = {"version": "Unknown", "check": binary["check"]} versions_by_host[host.config.address] = versions_at_host return versions_by_host def get_remote_binaries_versions(hosting: Hosting) -> dict[str, str]: versions_by_host = {} future_binary_verions = parallel(parallel_binary_verions, parallel_items=hosting.hosts) for future in future_binary_verions: versions_by_host.update(future.result()) # Consolidate versions across all hosts cheak_versions = {} exсeptions = [] exception = set() previous_host = None versions = {} captured_version = None for host, binary_versions in versions_by_host.items(): for name, binary in binary_versions.items(): version = binary["version"] if not cheak_versions.get(f'{name[:-2]}', None): captured_version = cheak_versions.get(f'{name[:-2]}',{}).get(host, {}).get(captured_version) cheak_versions[f'{name[:-2]}'] = {host: {version: name}} else: captured_version = list(cheak_versions.get(f'{name[:-2]}',{}).get(previous_host).keys())[0] cheak_versions[f'{name[:-2]}'].update({host:{version:name}}) if captured_version and captured_version != version: exception.add(name[:-2]) versions[name] = {"version": version, "check": binary["check"]} previous_host = host if exception: for i in exception: for host in versions_by_host.keys(): for version, name in cheak_versions.get(i).get(host).items(): exсeptions.append(f'Binary {name} has inconsistent version {version} on host {host}') exсeptions.append('\n') return versions, exсeptions def _parse_version(version_output: str) -> str: version = re.search(r"version[:\s]*v?(.+)", version_output, re.IGNORECASE) return version.group(1).strip() if version else version_output