def test_download_logs() -> None: """ This test verifies that available pod log files are correctly downloaded and formatted. """ # create the test object this: PodLogDownloader = PodLogDownloader( kubectl=KubectlTest(), output_dir="./test_download_logs") # run the method being tested logs_dir, timeout_pods, error_pods = this.download_logs() # verify the expected number of logs were written to disk assert len([ name for name in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, name)) ]) == 6 # define the expected and actual log files expected_log_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data", "expected-sas-annotations.txt") actual_log_file = os.path.join( logs_dir, f"{KubectlTest.Values.COMPONENT_SAS_ANNOTATIONS_POD_NAME}.log") # verify that a downloaded log contains the expected content assert filecmp.cmp(expected_log_file, actual_log_file) # clean up logs created above shutil.rmtree(os.path.abspath(os.path.join(logs_dir, "..")))
def test_init_invalid_process_wait_time() -> None: """ This test verifies that an AttributeError is raised when an invalid process_wait_time value is given. """ with pytest.raises(AttributeError) as except_info: PodLogDownloader(kubectl=KubectlTest(), wait=-5) assert "The wait value must be 0 or greater" in str(except_info.value)
def test_init_invalid_concurrent_processes() -> None: """ This test verifies that an AttributeError is raised when an invalid concurrent_processes value is given. """ with pytest.raises(AttributeError) as except_info: PodLogDownloader(kubectl=KubectlTest(), processes=0) assert "The processes value must be greater than 0" in str( except_info.value)
def test_init_default() -> None: """ This test verifies that the PodLogDownloader is constructed correctly with all default values. """ # create test object expected_kubectl: KubectlInterface = KubectlTest() actual: PodLogDownloader = PodLogDownloader(kubectl=expected_kubectl) # check for expected default values assert actual._kubectl == expected_kubectl assert actual._output_dir == PodLogDownloader.DEFAULT_OUTPUT_DIR assert actual._processes == PodLogDownloader.DEFAULT_PROCESSES assert actual._wait == PodLogDownloader.DEFAULT_WAIT
def test_download_logs_with_empty_deployment() -> None: """ This test verifies that a NoPodsError is raised when no pods are available in the deployment. """ # create the test object this: PodLogDownloader = PodLogDownloader(kubectl=KubectlTest( simulate_empty_deployment=True)) # cause the error with pytest.raises(NoPodsError) as except_info: this.download_logs() # make sure the correct error was raised assert isinstance(except_info.value, NoPodsError) assert "No pods were found in namespace [test]." in str(except_info.value)
def test_download_logs_with_invalid_selected_component() -> None: """ This test verifies that a NoMatchingPodsError is raised when the selected_components list doesn't contain an available pod. """ # create the test object this: PodLogDownloader = PodLogDownloader(kubectl=KubectlTest()) # cause the error with pytest.raises(NoMatchingPodsError) as except_info: this.download_logs(selected_components=["sas-foo"]) # make sure the correct error was raised assert isinstance(except_info.value, NoMatchingPodsError) assert "No pods in namespace [test] matched the provided components filter: [sas-foo]" in str( except_info.value)
def test_download_logs_with_list_pods_forbidden() -> None: """ This test verifies that a KubectlRequestForbiddenError is raised when pods cannot be listed. """ # create the test object this: PodLogDownloader = PodLogDownloader(kubectl=KubectlTest( namespace="foo")) # cause the error with pytest.raises(KubectlRequestForbiddenError) as except_info: this.download_logs() # make sure the correct error was raised assert isinstance(except_info.value, KubectlRequestForbiddenError) assert ( "Listing pods is forbidden in namespace [foo]. Make sure KUBECONFIG is correctly set and that the " "correct namespace is being targeted. A namespace can be given on the command line using the " "\"--namespace=\" option.") in str(except_info.value)
def test_init_custom() -> None: """ This test verifies that the PodLogDownloader is constructed correctly with all custom values. """ # define expected values expected_output_dir: Text = "./custom" expected_processes: int = 7 expected_wait: int = 45 # create test object expected_kubectl: KubectlInterface = KubectlTest() actual: PodLogDownloader = PodLogDownloader(kubectl=expected_kubectl, output_dir=expected_output_dir, processes=expected_processes, wait=expected_wait) # check for expected custom values assert actual._kubectl == expected_kubectl assert actual._output_dir == expected_output_dir assert actual._processes == expected_processes assert actual._wait == expected_wait
def main(argv: List): """ Implementation of the main script execution for the download-pod-logs command. :param argv: The parameters passed to the script at execution. """ # configure ArgumentParser arg_parser = ArgumentParser( prog=f"viya-ark.py {DownloadPodLogsCommand.command_name()}", description=DownloadPodLogsCommand.command_desc()) # add optional arguments # namespace arg_parser.add_argument( "-n", "--namespace", type=Text, default=None, dest="namespace", help= "Namespace to target containing SAS software, if not defined by KUBECONFIG." ) # output-dir arg_parser.add_argument( "-o", "--output-dir", type=Text, default=PodLogDownloader.DEFAULT_OUTPUT_DIR, dest="output_dir", help= f"Directory where log files will be written. Defaults to \"{PodLogDownloader.DEFAULT_OUTPUT_DIR}\"." ) # processes arg_parser.add_argument( "-p", "--processes", type=int, default=PodLogDownloader.DEFAULT_PROCESSES, dest="processes", help= "Number of simultaneous worker processes used to fetch logs. Defaults to " f"\"{PodLogDownloader.DEFAULT_PROCESSES}\".") # tail arg_parser.add_argument( "-t", "--tail", type=int, default=PodLogDownloader.DEFAULT_TAIL, dest="tail", help= f"Lines of recent log file to retrieve. Defaults to \"{PodLogDownloader.DEFAULT_TAIL}\"." ) # wait arg_parser.add_argument( "-w", "--wait", type=int, default=PodLogDownloader.DEFAULT_WAIT, dest="wait", help= "Wait time, in seconds, before terminating a log-gathering process. Defaults to " f"\"{PodLogDownloader.DEFAULT_WAIT}\".") arg_parser.add_argument( "--no-parse", action="store_true", dest="noparse", help="Download log files in original format without parsing.") # add positional arguments arg_parser.add_argument( "selected_components", default=None, nargs="*", help= "A space-separated list of SAS component names used to limit the logs downloaded. If no component names " "are provided, logs for all SAS components will be downloaded.") # parse the args passed to this command args = arg_parser.parse_args(argv) # initialize the kubectl object # this will also verify the connection to the cluster and if the namespace is valid, if provided try: kubectl: Kubectl = Kubectl(namespace=args.namespace) except ConnectionError as e: print() print(f"ERROR: {e}", file=sys.stderr) print() sys.exit(_CONNECTION_ERROR_RC_) except NamespaceNotFoundError as e: print() print(f"ERROR: {e}", file=sys.stderr) print() sys.exit(_NAMESPACE_NOT_FOUND_RC_) # create the log downloader try: log_downloader = PodLogDownloader(kubectl=kubectl, output_dir=args.output_dir, processes=args.processes, wait=args.wait) except AttributeError as e: print() print(f"ERROR: {e}", sys.stderr) print() sys.exit(_BAD_OPT_RC_) # download the logs try: print() with LRPIndicator(enter_message="Downloading pod logs"): log_dir, timeout_pods, error_pods = log_downloader.download_logs( selected_components=args.selected_components, tail=args.tail, noparse=args.noparse) # print any containers that encountered errors, if present if len(error_pods) > 0: print( "\nERROR: Log content for the following containers could not be downloaded:\n", file=sys.stderr) # print the containers that had an error for err_info in error_pods: if err_info[0]: print(f" [{err_info[0]}] in pod [{err_info[1]}]", file=sys.stderr) else: print(f" All containers in pod [{err_info[1]}]", file=sys.stderr) print( "\nContainer status information is available in the log file.", file=sys.stderr) # print any pods that timed out, if present if len(timeout_pods) > 0: print( "\nWARNING: Log content for the following pods was not downloaded because the configured wait time " f"was exceeded ({args.wait}s):\n") # print the pods that timed out for pod_name in timeout_pods: print(f" {pod_name}") print( "\nThe wait time can be increased using the \"--wait=\" option." ) print() # check folder is empty if len(os.listdir(log_dir)) == 0: os.rmdir(log_dir) print("No log files created.") else: # print output directory print(f"Log files created in: {log_dir}") print() except (NoMatchingPodsError, NoPodsError) as e: print() print(e) print() sys.exit(_SUCCESS_RC_) # exit successfully sys.exit(_SUCCESS_RC_)