From d6721742c4ee76ecd641021f77efa4ffc25012b2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 30 Nov 2025 12:49:51 +0100 Subject: [PATCH] test: new test, verify support data collection Signed-off-by: Joachim Wiberg --- test/case/misc/Readme.adoc | 9 + test/case/misc/all.yaml | 3 + test/case/misc/support_collect/Readme.adoc | 1 + test/case/misc/support_collect/test.adoc | 24 +++ test/case/misc/support_collect/test.py | 185 ++++++++++++++++++++ test/case/misc/support_collect/topology.dot | 1 + test/case/misc/support_collect/topology.svg | 33 ++++ 7 files changed, 256 insertions(+) create mode 120000 test/case/misc/support_collect/Readme.adoc create mode 100644 test/case/misc/support_collect/test.adoc create mode 100755 test/case/misc/support_collect/test.py create mode 120000 test/case/misc/support_collect/topology.dot create mode 100644 test/case/misc/support_collect/topology.svg diff --git a/test/case/misc/Readme.adoc b/test/case/misc/Readme.adoc index db79f21d..ff72130b 100644 --- a/test/case/misc/Readme.adoc +++ b/test/case/misc/Readme.adoc @@ -1,4 +1,13 @@ :testgroup: == Miscellaneous Tests +Tests verifying system utilities and operational features: + + - Operational datastore query for all configuration + - Support data collection with work-dir and GPG encryption + include::operational_all/Readme.adoc[] + +<<< + +include::support_collect/Readme.adoc[] diff --git a/test/case/misc/all.yaml b/test/case/misc/all.yaml index 1877e5ca..8db13da2 100644 --- a/test/case/misc/all.yaml +++ b/test/case/misc/all.yaml @@ -2,5 +2,8 @@ - name: Get operational case: operational_all/test.py +- name: Support data collection + case: support_collect/test.py + #- name: start_from_startup # case: start_from_startup.py diff --git a/test/case/misc/support_collect/Readme.adoc b/test/case/misc/support_collect/Readme.adoc new file mode 120000 index 00000000..ae32c841 --- /dev/null +++ b/test/case/misc/support_collect/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/misc/support_collect/test.adoc b/test/case/misc/support_collect/test.adoc new file mode 100644 index 00000000..c3d4f6a6 --- /dev/null +++ b/test/case/misc/support_collect/test.adoc @@ -0,0 +1,24 @@ +=== Support data collection + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/support_collect] + +==== Description + +Verify that the support collect command works and produces a valid tarball +with expected content. Tests both the --work-dir global option and GPG +encryption (when available on target). + +==== Topology + +image::topology.svg[Support data collection topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUT +. Check for GPG availability on target +. Run support collect with --work-dir and short log tail +. Verify tarball was created and is valid +. Run support collect with GPG encryption +. Verify encrypted file and decrypt it + + diff --git a/test/case/misc/support_collect/test.py b/test/case/misc/support_collect/test.py new file mode 100755 index 00000000..5713472f --- /dev/null +++ b/test/case/misc/support_collect/test.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Support data collection + +Verify that the support collect command works and produces a valid tarball +with expected content. Tests both the --work-dir global option and GPG +encryption (when available on target). + +""" + +import os +import subprocess +import tarfile +import tempfile +import infamy +import infamy.ssh as ssh + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + target = env.attach("target", "mgmt") + tgtssh = env.attach("target", "mgmt", "ssh") + + with test.step("Check for GPG availability on target"): + result = tgtssh.run("command -v gpg >/dev/null 2>&1", check=False) + has_gpg = (result.returncode == 0) + if has_gpg: + print("GPG is available on target - will test encryption") + else: + print("GPG not available on target - skipping encryption tests") + + with test.step("Run support collect with --work-dir and short log tail"): + # Create temporary file for output + with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as tmp: + output_file = tmp.name + + # Use /tmp as work-dir to test the --work-dir option + # Run support collect via SSH with short log tail for testing + # Capture stdout (the tarball) to file + # Note: timeout is generous to handle systems with many network ports + # (ethtool collection scales with number of interfaces) + with open(output_file, 'wb') as f: + result = tgtssh.run("support --work-dir /tmp collect --log-sec 2", + stdout=f, + stderr=subprocess.PIPE, + timeout=120) + + if result.returncode != 0: + stderr_output = result.stderr.decode('utf-8') if result.stderr else "" + print(f"support collect failed with return code {result.returncode}") + print(f"stderr: {stderr_output}") + raise Exception("support collect command failed") + + with test.step("Verify tarball was created and is valid"): + if not os.path.exists(output_file): + raise Exception(f"Output file {output_file} was not created") + + file_size = os.path.getsize(output_file) + if file_size == 0: + raise Exception("Output tarball is empty") + + print(f"Tarball created: {file_size} bytes") + + # Verify it's a valid tar.gz + try: + with tarfile.open(output_file, 'r:gz') as tar: + members = tar.getnames() + print(f"Tarball contains {len(members)} files/directories") + + # Verify some expected files exist + expected_files = [ + 'collection.log', + 'operational-config.json', + 'system/dmesg.txt', + 'system/meminfo.txt', + 'network/ip/addr.json' + ] + + root_dir = members[0] if members else None + for expected in expected_files: + full_path = f"{root_dir}/{expected}" if root_dir else expected + if full_path not in members: + print(f"Warning: Expected file '{expected}' not found in tarball") + else: + print(f"Found: {expected}") + + except tarfile.TarError as e: + raise Exception(f"Invalid tarball: {e}") + + finally: + # Clean up + if os.path.exists(output_file): + os.remove(output_file) + + if has_gpg: + with test.step("Run support collect with GPG encryption"): + # Create temporary file for encrypted output + with tempfile.NamedTemporaryFile(suffix=".tar.gz.gpg", delete=False) as tmp: + encrypted_file = tmp.name + + # Use a test password + test_password = "test-support-password-123" + + # Run support collect with encryption + with open(encrypted_file, 'wb') as f: + result = tgtssh.run(f"support --work-dir /tmp collect --log-sec 2 --password {test_password}", + stdout=f, + stderr=subprocess.PIPE, + timeout=120) + + if result.returncode != 0: + stderr_output = result.stderr.decode('utf-8') if result.stderr else "" + print(f"support collect with encryption failed: {stderr_output}") + raise Exception("support collect with --password failed") + + with test.step("Verify encrypted file and decrypt it"): + if not os.path.exists(encrypted_file): + raise Exception(f"Encrypted output file {encrypted_file} was not created") + + file_size = os.path.getsize(encrypted_file) + if file_size == 0: + raise Exception("Encrypted output file is empty") + + print(f"Encrypted file created: {file_size} bytes") + + # Create temporary file for decrypted output + with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as tmp: + decrypted_file = tmp.name + + try: + # Decrypt the file using gpg + with open(encrypted_file, 'rb') as ef: + with open(decrypted_file, 'wb') as df: + decrypt_result = subprocess.run( + ["gpg", "--batch", "--yes", "--passphrase", test_password, + "--pinentry-mode", "loopback", "-d"], + stdin=ef, + stdout=df, + stderr=subprocess.PIPE, + timeout=30 + ) + + if decrypt_result.returncode != 0: + stderr_output = decrypt_result.stderr.decode('utf-8') if decrypt_result.stderr else "" + print(f"GPG decryption failed: {stderr_output}") + raise Exception("Failed to decrypt GPG-encrypted support data") + + print("Successfully decrypted GPG file") + + # Verify the decrypted file is a valid tarball + with tarfile.open(decrypted_file, 'r:gz') as tar: + members = tar.getnames() + print(f"Decrypted tarball contains {len(members)} files/directories") + + # Verify some expected files exist + expected_files = [ + 'collection.log', + 'operational-config.json', + 'system/dmesg.txt' + ] + + root_dir = members[0] if members else None + for expected in expected_files: + full_path = f"{root_dir}/{expected}" if root_dir else expected + if full_path not in members: + print(f"Warning: Expected file '{expected}' not found in decrypted tarball") + else: + print(f"Found in decrypted tarball: {expected}") + + except tarfile.TarError as e: + raise Exception(f"Decrypted file is not a valid tarball: {e}") + + except subprocess.TimeoutExpired: + raise Exception("GPG decryption timed out") + + except FileNotFoundError: + print("Warning: gpg not available on host system - skipping decryption verification") + + finally: + # Clean up + if os.path.exists(encrypted_file): + os.remove(encrypted_file) + if os.path.exists(decrypted_file): + os.remove(decrypted_file) + + test.succeed() diff --git a/test/case/misc/support_collect/topology.dot b/test/case/misc/support_collect/topology.dot new file mode 120000 index 00000000..02b78869 --- /dev/null +++ b/test/case/misc/support_collect/topology.dot @@ -0,0 +1 @@ +../../../infamy/topologies/1x1.dot \ No newline at end of file diff --git a/test/case/misc/support_collect/topology.svg b/test/case/misc/support_collect/topology.svg new file mode 100644 index 00000000..6fc6f47a --- /dev/null +++ b/test/case/misc/support_collect/topology.svg @@ -0,0 +1,33 @@ + + + + + + +1x1 + + + +host + +host + +mgmt + + + +target + +mgmt + +target + + + +host:mgmt--target:mgmt + + + +