Beispiel #1
0
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, "..")))
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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
Beispiel #9
0
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_)