# Copyright 2016 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Entry point for running stress tests."""

import argparse
from concurrent import futures
import queue
import threading

import grpc

from src.proto.grpc.testing import metrics_pb2_grpc
from src.proto.grpc.testing import test_pb2_grpc
from tests.interop import methods
from tests.interop import resources
from tests.qps import histogram
from tests.stress import metrics_server
from tests.stress import test_runner


def _args():
    parser = argparse.ArgumentParser(
        description="gRPC Python stress test client"
    )
    parser.add_argument(
        "--server_addresses",
        help="comma separated list of hostname:port to run servers on",
        default="localhost:8080",
        type=str,
    )
    parser.add_argument(
        "--test_cases",
        help="comma separated list of testcase:weighting of tests to run",
        default="large_unary:100",
        type=str,
    )
    parser.add_argument(
        "--test_duration_secs",
        help="number of seconds to run the stress test",
        default=-1,
        type=int,
    )
    parser.add_argument(
        "--num_channels_per_server",
        help="number of channels per server",
        default=1,
        type=int,
    )
    parser.add_argument(
        "--num_stubs_per_channel",
        help="number of stubs to create per channel",
        default=1,
        type=int,
    )
    parser.add_argument(
        "--metrics_port",
        help="the port to listen for metrics requests on",
        default=8081,
        type=int,
    )
    parser.add_argument(
        "--use_test_ca",
        help="Whether to use our fake CA. Requires --use_tls=true",
        default=False,
        type=bool,
    )
    parser.add_argument(
        "--use_tls", help="Whether to use TLS", default=False, type=bool
    )
    parser.add_argument(
        "--server_host_override",
        help="the server host to which to claim to connect",
        type=str,
    )
    return parser.parse_args()


def _test_case_from_arg(test_case_arg):
    for test_case in methods.TestCase:
        if test_case_arg == test_case.value:
            return test_case
    else:
        raise ValueError("No test case {}!".format(test_case_arg))


def _parse_weighted_test_cases(test_case_args):
    weighted_test_cases = {}
    for test_case_arg in test_case_args.split(","):
        name, weight = test_case_arg.split(":", 1)
        test_case = _test_case_from_arg(name)
        weighted_test_cases[test_case] = int(weight)
    return weighted_test_cases


def _get_channel(target, args):
    if args.use_tls:
        if args.use_test_ca:
            root_certificates = resources.test_root_certificates()
        else:
            root_certificates = None  # will load default roots.
        channel_credentials = grpc.ssl_channel_credentials(
            root_certificates=root_certificates
        )
        options = (
            (
                "grpc.ssl_target_name_override",
                args.server_host_override,
            ),
        )
        channel = grpc.secure_channel(
            target, channel_credentials, options=options
        )
    else:
        channel = grpc.insecure_channel(target)

    # waits for the channel to be ready before we start sending messages
    grpc.channel_ready_future(channel).result()
    return channel


def run_test(args):
    test_cases = _parse_weighted_test_cases(args.test_cases)
    test_server_targets = args.server_addresses.split(",")
    # Propagate any client exceptions with a queue
    exception_queue = queue.Queue()
    stop_event = threading.Event()
    hist = histogram.Histogram(1, 1)
    runners = []

    server = grpc.server(futures.ThreadPoolExecutor(max_workers=25))
    metrics_pb2_grpc.add_MetricsServiceServicer_to_server(
        metrics_server.MetricsServer(hist), server
    )
    server.add_insecure_port("[::]:{}".format(args.metrics_port))
    server.start()

    for test_server_target in test_server_targets:
        for _ in range(args.num_channels_per_server):
            channel = _get_channel(test_server_target, args)
            for _ in range(args.num_stubs_per_channel):
                stub = test_pb2_grpc.TestServiceStub(channel)
                runner = test_runner.TestRunner(
                    stub, test_cases, hist, exception_queue, stop_event
                )
                runners.append(runner)

    for runner in runners:
        runner.start()
    try:
        timeout_secs = args.test_duration_secs
        if timeout_secs < 0:
            timeout_secs = None
        raise exception_queue.get(block=True, timeout=timeout_secs)
    except queue.Empty:
        # No exceptions thrown, success
        pass
    finally:
        stop_event.set()
        for runner in runners:
            runner.join()
        runner = None
        server.stop(None)


if __name__ == "__main__":
    run_test(_args())
