aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/run_tests
diff options
context:
space:
mode:
Diffstat (limited to 'tools/run_tests')
-rw-r--r--tools/run_tests/distribtest_targets.py11
-rwxr-xr-xtools/run_tests/jobset.py3
-rw-r--r--tools/run_tests/post_test_node.bat30
-rw-r--r--tools/run_tests/report_utils.py2
-rwxr-xr-xtools/run_tests/run_tests.py5
-rw-r--r--tools/run_tests/sources_and_headers.json2
-rwxr-xr-xtools/run_tests/stress_test/run_stress_tests_on_gke.py556
7 files changed, 604 insertions, 5 deletions
diff --git a/tools/run_tests/distribtest_targets.py b/tools/run_tests/distribtest_targets.py
index 933103f0a0..34cc1cd710 100644
--- a/tools/run_tests/distribtest_targets.py
+++ b/tools/run_tests/distribtest_targets.py
@@ -96,6 +96,15 @@ class CSharpDistribTest(object):
return create_jobspec(self.name,
['test/distrib/csharp/run_distrib_test.sh'],
environ={'EXTERNAL_GIT_ROOT': '../../..'})
+ elif self.platform == 'windows':
+ if self.arch == 'x64':
+ environ={'MSBUILD_EXTRA_ARGS': '/p:Platform=x64',
+ 'DISTRIBTEST_OUTPATH': 'DistribTest\\bin\\x64\\Debug'}
+ else:
+ environ={'DISTRIBTEST_OUTPATH': 'DistribTest\\bin\\\Debug'}
+ return create_jobspec(self.name,
+ ['test\\distrib\\csharp\\run_distrib_test.bat'],
+ environ=environ)
else:
raise Exception("Not supported yet.")
@@ -240,6 +249,8 @@ def targets():
CSharpDistribTest('linux', 'x64', 'ubuntu1510'),
CSharpDistribTest('linux', 'x64', 'ubuntu1604'),
CSharpDistribTest('macos', 'x86'),
+ CSharpDistribTest('windows', 'x86'),
+ CSharpDistribTest('windows', 'x64'),
PythonDistribTest('linux', 'x64', 'wheezy'),
PythonDistribTest('linux', 'x64', 'jessie'),
PythonDistribTest('linux', 'x86', 'jessie'),
diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index adf178bb3c..a3b246dc08 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -384,7 +384,8 @@ class Jobset(object):
self._travis,
self._add_env)
self._running.add(job)
- self.resultset[job.GetSpec().shortname] = []
+ if not self.resultset.has_key(job.GetSpec().shortname):
+ self.resultset[job.GetSpec().shortname] = []
return True
def reap(self):
diff --git a/tools/run_tests/post_test_node.bat b/tools/run_tests/post_test_node.bat
new file mode 100644
index 0000000000..1a2a5491fa
--- /dev/null
+++ b/tools/run_tests/post_test_node.bat
@@ -0,0 +1,30 @@
+@rem Copyright 2016, Google Inc.
+@rem All rights reserved.
+@rem
+@rem Redistribution and use in source and binary forms, with or without
+@rem modification, are permitted provided that the following conditions are
+@rem met:
+@rem
+@rem * Redistributions of source code must retain the above copyright
+@rem notice, this list of conditions and the following disclaimer.
+@rem * Redistributions in binary form must reproduce the above
+@rem copyright notice, this list of conditions and the following disclaimer
+@rem in the documentation and/or other materials provided with the
+@rem distribution.
+@rem * Neither the name of Google Inc. nor the names of its
+@rem contributors may be used to endorse or promote products derived from
+@rem this software without specific prior written permission.
+@rem
+@rem THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+@rem "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+@rem LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@rem A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+@rem OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+@rem SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+@rem LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+@rem DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+@rem THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+@rem (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+@rem OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+rmdir node_modules /S /Q \ No newline at end of file
diff --git a/tools/run_tests/report_utils.py b/tools/run_tests/report_utils.py
index 0032a98523..df114e5dae 100644
--- a/tools/run_tests/report_utils.py
+++ b/tools/run_tests/report_utils.py
@@ -47,7 +47,7 @@ def _filter_msg(msg, output_format):
# that make XML report unparseable.
filtered_msg = filter(
lambda x: x in string.printable and x != '\f' and x != '\v',
- msg.decode(errors='ignore'))
+ msg.decode('UTF-8', 'ignore'))
if output_format == 'HTML':
filtered_msg = filtered_msg.replace('"', '"')
return filtered_msg
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 75de4cb71d..08a5ff0e8f 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -290,7 +290,10 @@ class NodeLanguage(object):
return [['tools/run_tests/build_node.sh', self.node_version]]
def post_tests_steps(self):
- return []
+ if self.platform == 'windows':
+ return [['tools\\run_tests\\post_test_node.bat']]
+ else:
+ return []
def makefile_name(self):
return 'Makefile'
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 6b70f091eb..0c7a6c7b5f 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -5041,7 +5041,6 @@
"src/cpp/client/secure_credentials.cc",
"src/cpp/client/secure_credentials.h",
"src/cpp/codegen/grpc_library.cc",
- "src/cpp/common/alarm.cc",
"src/cpp/common/auth_property_iterator.cc",
"src/cpp/common/call.cc",
"src/cpp/common/channel_arguments.cc",
@@ -5300,7 +5299,6 @@
"src/cpp/client/generic_stub.cc",
"src/cpp/client/insecure_credentials.cc",
"src/cpp/codegen/grpc_library.cc",
- "src/cpp/common/alarm.cc",
"src/cpp/common/call.cc",
"src/cpp/common/channel_arguments.cc",
"src/cpp/common/completion_queue.cc",
diff --git a/tools/run_tests/stress_test/run_stress_tests_on_gke.py b/tools/run_tests/stress_test/run_stress_tests_on_gke.py
new file mode 100755
index 0000000000..634eb1aca5
--- /dev/null
+++ b/tools/run_tests/stress_test/run_stress_tests_on_gke.py
@@ -0,0 +1,556 @@
+#!/usr/bin/env python2.7
+# Copyright 2015-2016, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import argparse
+import datetime
+import os
+import subprocess
+import sys
+import time
+
+stress_test_utils_dir = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '../../gcp/stress_test'))
+sys.path.append(stress_test_utils_dir)
+from stress_test_utils import BigQueryHelper
+
+kubernetes_api_dir = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '../../gcp/utils'))
+sys.path.append(kubernetes_api_dir)
+
+import kubernetes_api
+
+_GRPC_ROOT = os.path.abspath(os.path.join(
+ os.path.dirname(sys.argv[0]), '../../..'))
+os.chdir(_GRPC_ROOT)
+
+# num of seconds to wait for the GKE image to start and warmup
+_GKE_IMAGE_WARMUP_WAIT_SECS = 60
+
+_SERVER_POD_NAME = 'stress-server'
+_CLIENT_POD_NAME_PREFIX = 'stress-client'
+_DATASET_ID_PREFIX = 'stress_test'
+_SUMMARY_TABLE_ID = 'summary'
+_QPS_TABLE_ID = 'qps'
+
+_DEFAULT_DOCKER_IMAGE_NAME = 'grpc_stress_test'
+
+# The default port on which the kubernetes proxy server is started on localhost
+# (i.e kubectl proxy --port=<port>)
+_DEFAULT_KUBERNETES_PROXY_PORT = 8001
+
+# How frequently should the stress client wrapper script (running inside a GKE
+# container) poll the health of the stress client (also running inside the GKE
+# container) and upload metrics to BigQuery
+_DEFAULT_STRESS_CLIENT_POLL_INTERVAL_SECS = 60
+
+# The default setting for stress test server and client
+_DEFAULT_STRESS_SERVER_PORT = 8080
+_DEFAULT_METRICS_PORT = 8081
+_DEFAULT_TEST_CASES_STR = 'empty_unary:1,large_unary:1,client_streaming:1,server_streaming:1,empty_stream:1'
+_DEFAULT_NUM_CHANNELS_PER_SERVER = 5
+_DEFAULT_NUM_STUBS_PER_CHANNEL = 10
+_DEFAULT_METRICS_COLLECTION_INTERVAL_SECS = 30
+
+# Number of stress client instances to launch
+_DEFAULT_NUM_CLIENTS = 3
+
+# How frequently should this test monitor the health of Stress clients and
+# Servers running in GKE
+_DEFAULT_TEST_POLL_INTERVAL_SECS = 60
+
+# Default run time for this test (2 hour)
+_DEFAULT_TEST_DURATION_SECS = 7200
+
+# The number of seconds it would take a GKE pod to warm up (i.e get to 'Running'
+# state from the time of creation). Ideally this is something the test should
+# automatically determine by using Kubernetes API to poll the pods status.
+_DEFAULT_GKE_WARMUP_SECS = 60
+
+
+class KubernetesProxy:
+ """ Class to start a proxy on localhost to the Kubernetes API server """
+
+ def __init__(self, api_port):
+ self.port = api_port
+ self.p = None
+ self.started = False
+
+ def start(self):
+ cmd = ['kubectl', 'proxy', '--port=%d' % self.port]
+ self.p = subprocess.Popen(args=cmd)
+ self.started = True
+ time.sleep(2)
+ print '..Started'
+
+ def get_port(self):
+ return self.port
+
+ def is_started(self):
+ return self.started
+
+ def __del__(self):
+ if self.p is not None:
+ print 'Shutting down Kubernetes proxy..'
+ self.p.kill()
+
+
+class TestSettings:
+
+ def __init__(self, build_docker_image, test_poll_interval_secs,
+ test_duration_secs, kubernetes_proxy_port):
+ self.build_docker_image = build_docker_image
+ self.test_poll_interval_secs = test_poll_interval_secs
+ self.test_duration_secs = test_duration_secs
+ self.kubernetes_proxy_port = kubernetes_proxy_port
+
+
+class GkeSettings:
+
+ def __init__(self, project_id, docker_image_name):
+ self.project_id = project_id
+ self.docker_image_name = docker_image_name
+ self.tag_name = 'gcr.io/%s/%s' % (project_id, docker_image_name)
+
+
+class BigQuerySettings:
+
+ def __init__(self, run_id, dataset_id, summary_table_id, qps_table_id):
+ self.run_id = run_id
+ self.dataset_id = dataset_id
+ self.summary_table_id = summary_table_id
+ self.qps_table_id = qps_table_id
+
+
+class StressServerSettings:
+
+ def __init__(self, server_pod_name, server_port):
+ self.server_pod_name = server_pod_name
+ self.server_port = server_port
+
+
+class StressClientSettings:
+
+ def __init__(self, num_clients, client_pod_name_prefix, server_pod_name,
+ server_port, metrics_port, metrics_collection_interval_secs,
+ stress_client_poll_interval_secs, num_channels_per_server,
+ num_stubs_per_channel, test_cases_str):
+ self.num_clients = num_clients
+ self.client_pod_name_prefix = client_pod_name_prefix
+ self.server_pod_name = server_pod_name
+ self.server_port = server_port
+ self.metrics_port = metrics_port
+ self.metrics_collection_interval_secs = metrics_collection_interval_secs
+ self.stress_client_poll_interval_secs = stress_client_poll_interval_secs
+ self.num_channels_per_server = num_channels_per_server
+ self.num_stubs_per_channel = num_stubs_per_channel
+ self.test_cases_str = test_cases_str
+
+ # == Derived properties ==
+ # Note: Client can accept a list of server addresses (a comma separated list
+ # of 'server_name:server_port'). In this case, we only have one server
+ # address to pass
+ self.server_addresses = '%s.default.svc.cluster.local:%d' % (
+ server_pod_name, server_port)
+ self.client_pod_names_list = ['%s-%d' % (client_pod_name_prefix, i)
+ for i in range(1, num_clients + 1)]
+
+
+def _build_docker_image(image_name, tag_name):
+ """ Build the docker image and add tag it to the GKE repository """
+ print 'Building docker image: %s' % image_name
+ os.environ['INTEROP_IMAGE'] = image_name
+ os.environ['INTEROP_IMAGE_REPOSITORY_TAG'] = tag_name
+ # Note that 'BASE_NAME' HAS to be 'grpc_interop_stress_cxx' since the script
+ # build_interop_stress_image.sh invokes the following script:
+ # tools/dockerfile/$BASE_NAME/build_interop_stress.sh
+ os.environ['BASE_NAME'] = 'grpc_interop_stress_cxx'
+ cmd = ['tools/jenkins/build_interop_stress_image.sh']
+ retcode = subprocess.call(args=cmd)
+ if retcode != 0:
+ print 'Error in building docker image'
+ return False
+ return True
+
+
+def _push_docker_image_to_gke_registry(docker_tag_name):
+ """Executes 'gcloud docker push <docker_tag_name>' to push the image to GKE registry"""
+ cmd = ['gcloud', 'docker', 'push', docker_tag_name]
+ print 'Pushing %s to GKE registry..' % docker_tag_name
+ retcode = subprocess.call(args=cmd)
+ if retcode != 0:
+ print 'Error in pushing docker image %s to the GKE registry' % docker_tag_name
+ return False
+ return True
+
+
+def _launch_server(gke_settings, stress_server_settings, bq_settings,
+ kubernetes_proxy):
+ """ Launches a stress test server instance in GKE cluster """
+ if not kubernetes_proxy.is_started:
+ print 'Kubernetes proxy must be started before calling this function'
+ return False
+
+ # This is the wrapper script that is run in the container. This script runs
+ # the actual stress test server
+ server_cmd_list = ['/var/local/git/grpc/tools/gcp/stress_test/run_server.py']
+
+ # run_server.py does not take any args from the command line. The args are
+ # instead passed via environment variables (see server_env below)
+ server_arg_list = []
+
+ # The parameters to the script run_server.py are injected into the container
+ # via environment variables
+ server_env = {
+ 'STRESS_TEST_IMAGE_TYPE': 'SERVER',
+ 'STRESS_TEST_IMAGE': '/var/local/git/grpc/bins/opt/interop_server',
+ 'STRESS_TEST_ARGS_STR': '--port=%s' % stress_server_settings.server_port,
+ 'RUN_ID': bq_settings.run_id,
+ 'POD_NAME': stress_server_settings.server_pod_name,
+ 'GCP_PROJECT_ID': gke_settings.project_id,
+ 'DATASET_ID': bq_settings.dataset_id,
+ 'SUMMARY_TABLE_ID': bq_settings.summary_table_id,
+ 'QPS_TABLE_ID': bq_settings.qps_table_id
+ }
+
+ # Launch Server
+ is_success = kubernetes_api.create_pod_and_service(
+ 'localhost',
+ kubernetes_proxy.get_port(),
+ 'default', # Use 'default' namespace
+ stress_server_settings.server_pod_name,
+ gke_settings.tag_name,
+ [stress_server_settings.server_port], # Port that should be exposed
+ server_cmd_list,
+ server_arg_list,
+ server_env,
+ True # Headless = True for server. Since we want DNS records to be created by GKE
+ )
+
+ return is_success
+
+
+def _launch_client(gke_settings, stress_server_settings, stress_client_settings,
+ bq_settings, kubernetes_proxy):
+ """ Launches a configurable number of stress test clients on GKE cluster """
+ if not kubernetes_proxy.is_started:
+ print 'Kubernetes proxy must be started before calling this function'
+ return False
+
+ stress_client_arg_list = [
+ '--server_addresses=%s' % stress_client_settings.server_addresses,
+ '--test_cases=%s' % stress_client_settings.test_cases_str,
+ '--num_stubs_per_channel=%d' %
+ stress_client_settings.num_stubs_per_channel
+ ]
+
+ # This is the wrapper script that is run in the container. This script runs
+ # the actual stress client
+ client_cmd_list = ['/var/local/git/grpc/tools/gcp/stress_test/run_client.py']
+
+ # run_client.py takes no args. All args are passed as env variables (see
+ # client_env)
+ client_arg_list = []
+
+ metrics_server_address = 'localhost:%d' % stress_client_settings.metrics_port
+ metrics_client_arg_list = [
+ '--metrics_server_address=%s' % metrics_server_address,
+ '--total_only=true'
+ ]
+
+ # The parameters to the script run_client.py are injected into the container
+ # via environment variables
+ client_env = {
+ 'STRESS_TEST_IMAGE_TYPE': 'CLIENT',
+ 'STRESS_TEST_IMAGE': '/var/local/git/grpc/bins/opt/stress_test',
+ 'STRESS_TEST_ARGS_STR': ' '.join(stress_client_arg_list),
+ 'METRICS_CLIENT_IMAGE': '/var/local/git/grpc/bins/opt/metrics_client',
+ 'METRICS_CLIENT_ARGS_STR': ' '.join(metrics_client_arg_list),
+ 'RUN_ID': bq_settings.run_id,
+ 'POLL_INTERVAL_SECS':
+ str(stress_client_settings.stress_client_poll_interval_secs),
+ 'GCP_PROJECT_ID': gke_settings.project_id,
+ 'DATASET_ID': bq_settings.dataset_id,
+ 'SUMMARY_TABLE_ID': bq_settings.summary_table_id,
+ 'QPS_TABLE_ID': bq_settings.qps_table_id
+ }
+
+ for pod_name in stress_client_settings.client_pod_names_list:
+ client_env['POD_NAME'] = pod_name
+ is_success = kubernetes_api.create_pod_and_service(
+ 'localhost', # Since proxy is running on localhost
+ kubernetes_proxy.get_port(),
+ 'default', # default namespace
+ pod_name,
+ gke_settings.tag_name,
+ [stress_client_settings.metrics_port
+ ], # Client pods expose metrics port
+ client_cmd_list,
+ client_arg_list,
+ client_env,
+ False # Client is not a headless service
+ )
+ if not is_success:
+ print 'Error in launching client %s' % pod_name
+ return False
+
+ return True
+
+
+def _launch_server_and_client(gke_settings, stress_server_settings,
+ stress_client_settings, bq_settings,
+ kubernetes_proxy_port):
+ # Start kubernetes proxy
+ print 'Kubernetes proxy'
+ kubernetes_proxy = KubernetesProxy(kubernetes_proxy_port)
+ kubernetes_proxy.start()
+
+ print 'Launching server..'
+ is_success = _launch_server(gke_settings, stress_server_settings, bq_settings,
+ kubernetes_proxy)
+ if not is_success:
+ print 'Error in launching server'
+ return False
+
+ # Server takes a while to start.
+ # TODO(sree) Use Kubernetes API to query the status of the server instead of
+ # sleeping
+ print 'Waiting for %s seconds for the server to start...' % _GKE_IMAGE_WARMUP_WAIT_SECS
+ time.sleep(_GKE_IMAGE_WARMUP_WAIT_SECS)
+
+ # Launch client
+ client_pod_name_prefix = 'stress-client'
+ is_success = _launch_client(gke_settings, stress_server_settings,
+ stress_client_settings, bq_settings,
+ kubernetes_proxy)
+
+ if not is_success:
+ print 'Error in launching client(s)'
+ return False
+
+ print 'Waiting for %s seconds for the client images to start...' % _GKE_IMAGE_WARMUP_WAIT_SECS
+ time.sleep(_GKE_IMAGE_WARMUP_WAIT_SECS)
+ return True
+
+
+def _delete_server_and_client(stress_server_settings, stress_client_settings,
+ kubernetes_proxy_port):
+ kubernetes_proxy = KubernetesProxy(kubernetes_proxy_port)
+ kubernetes_proxy.start()
+
+ # Delete clients first
+ is_success = True
+ for pod_name in stress_client_settings.client_pod_names_list:
+ is_success = kubernetes_api.delete_pod_and_service(
+ 'localhost', kubernetes_proxy_port, 'default', pod_name)
+ if not is_success:
+ return False
+
+ # Delete server
+ is_success = kubernetes_api.delete_pod_and_service(
+ 'localhost', kubernetes_proxy_port, 'default',
+ stress_server_settings.server_pod_name)
+ return is_success
+
+
+def run_test_main(test_settings, gke_settings, stress_server_settings,
+ stress_client_clients):
+ is_success = True
+
+ if test_settings.build_docker_image:
+ is_success = _build_docker_image(gke_settings.docker_image_name,
+ gke_settings.tag_name)
+ if not is_success:
+ return False
+
+ is_success = _push_docker_image_to_gke_registry(gke_settings.tag_name)
+ if not is_success:
+ return False
+
+ # Create a unique id for this run (Note: Using timestamp instead of UUID to
+ # make it easier to deduce the date/time of the run just by looking at the run
+ # run id. This is useful in debugging when looking at records in Biq query)
+ run_id = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
+ dataset_id = '%s_%s' % (_DATASET_ID_PREFIX, run_id)
+
+ # Big Query settings (common for both Stress Server and Client)
+ bq_settings = BigQuerySettings(run_id, dataset_id, _SUMMARY_TABLE_ID,
+ _QPS_TABLE_ID)
+
+ bq_helper = BigQueryHelper(run_id, '', '', args.project_id, dataset_id,
+ _SUMMARY_TABLE_ID, _QPS_TABLE_ID)
+ bq_helper.initialize()
+
+ try:
+ is_success = _launch_server_and_client(gke_settings, stress_server_settings,
+ stress_client_settings, bq_settings,
+ test_settings.kubernetes_proxy_port)
+ if not is_success:
+ return False
+
+ start_time = datetime.datetime.now()
+ end_time = start_time + datetime.timedelta(
+ seconds=test_settings.test_duration_secs)
+ print 'Running the test until %s' % end_time.isoformat()
+
+ while True:
+ if datetime.datetime.now() > end_time:
+ print 'Test was run for %d seconds' % test_settings.test_duration_secs
+ break
+
+ # Check if either stress server or clients have failed
+ if bq_helper.check_if_any_tests_failed():
+ is_success = False
+ print 'Some tests failed.'
+ break
+
+ # Things seem to be running fine. Wait until next poll time to check the
+ # status
+ print 'Sleeping for %d seconds..' % test_settings.test_poll_interval_secs
+ time.sleep(test_settings.test_poll_interval_secs)
+
+ # Print BiqQuery tables
+ bq_helper.print_summary_records()
+ bq_helper.print_qps_records()
+
+ finally:
+ # If is_success is False at this point, it means that the stress tests were
+ # started successfully but failed while running the tests. In this case we
+ # do should not delete the pods (since they contain all the failure
+ # information)
+ if is_success:
+ _delete_server_and_client(stress_server_settings, stress_client_settings,
+ test_settings.kubernetes_proxy_port)
+
+ return is_success
+
+
+argp = argparse.ArgumentParser(
+ description='Launch stress tests in GKE',
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+argp.add_argument('--project_id',
+ required=True,
+ help='The Google Cloud Platform Project Id')
+argp.add_argument('--num_clients',
+ default=1,
+ type=int,
+ help='Number of client instances to start')
+argp.add_argument('--docker_image_name',
+ default=_DEFAULT_DOCKER_IMAGE_NAME,
+ help='The name of the docker image containing stress client '
+ 'and stress servers')
+argp.add_argument('--build_docker_image',
+ dest='build_docker_image',
+ action='store_true',
+ help='Build a docker image and push to Google Container '
+ 'Registry')
+argp.add_argument('--do_not_build_docker_image',
+ dest='build_docker_image',
+ action='store_false',
+ help='Do not build and push docker image to Google Container '
+ 'Registry')
+argp.set_defaults(build_docker_image=True)
+
+argp.add_argument('--test_poll_interval_secs',
+ default=_DEFAULT_TEST_POLL_INTERVAL_SECS,
+ type=int,
+ help='How frequently should this script should monitor the '
+ 'health of stress clients and servers running in the GKE '
+ 'cluster')
+argp.add_argument('--test_duration_secs',
+ default=_DEFAULT_TEST_DURATION_SECS,
+ type=int,
+ help='How long should this test be run')
+argp.add_argument('--kubernetes_proxy_port',
+ default=_DEFAULT_KUBERNETES_PROXY_PORT,
+ type=int,
+ help='The port on which the kubernetes proxy (on localhost)'
+ ' is started')
+argp.add_argument('--stress_server_port',
+ default=_DEFAULT_STRESS_SERVER_PORT,
+ type=int,
+ help='The port on which the stress server (in GKE '
+ 'containers) listens')
+argp.add_argument('--stress_client_metrics_port',
+ default=_DEFAULT_METRICS_PORT,
+ type=int,
+ help='The port on which the stress clients (in GKE '
+ 'containers) expose metrics')
+argp.add_argument('--stress_client_poll_interval_secs',
+ default=_DEFAULT_STRESS_CLIENT_POLL_INTERVAL_SECS,
+ type=int,
+ help='How frequently should the stress client wrapper script'
+ ' running inside GKE should monitor health of the actual '
+ ' stress client process and upload the metrics to BigQuery')
+argp.add_argument('--stress_client_metrics_collection_interval_secs',
+ default=_DEFAULT_METRICS_COLLECTION_INTERVAL_SECS,
+ type=int,
+ help='How frequently should metrics be collected in-memory on'
+ ' the stress clients (running inside GKE containers). Note '
+ 'that this is NOT the same as the upload-to-BigQuery '
+ 'frequency. The metrics upload frequency is controlled by the'
+ ' --stress_client_poll_interval_secs flag')
+argp.add_argument('--stress_client_num_channels_per_server',
+ default=_DEFAULT_NUM_CHANNELS_PER_SERVER,
+ type=int,
+ help='The number of channels created to each server from a '
+ 'stress client')
+argp.add_argument('--stress_client_num_stubs_per_channel',
+ default=_DEFAULT_NUM_STUBS_PER_CHANNEL,
+ type=int,
+ help='The number of stubs created per channel. This number '
+ 'indicates the max number of RPCs that can be made in '
+ 'parallel on each channel at any given time')
+argp.add_argument('--stress_client_test_cases',
+ default=_DEFAULT_TEST_CASES_STR,
+ help='List of test cases (with weights) to be executed by the'
+ ' stress test client. The list is in the following format:\n'
+ ' <testcase_1:w_1,<test_case2:w_2>..<testcase_n:w_n>\n'
+ ' (Note: The weights do not have to add up to 100)')
+
+if __name__ == '__main__':
+ args = argp.parse_args()
+
+ test_settings = TestSettings(
+ args.build_docker_image, args.test_poll_interval_secs,
+ args.test_duration_secs, args.kubernetes_proxy_port)
+
+ gke_settings = GkeSettings(args.project_id, args.docker_image_name)
+
+ stress_server_settings = StressServerSettings(_SERVER_POD_NAME,
+ args.stress_server_port)
+ stress_client_settings = StressClientSettings(
+ args.num_clients, _CLIENT_POD_NAME_PREFIX, _SERVER_POD_NAME,
+ args.stress_server_port, args.stress_client_metrics_port,
+ args.stress_client_metrics_collection_interval_secs,
+ args.stress_client_poll_interval_secs,
+ args.stress_client_num_channels_per_server,
+ args.stress_client_num_stubs_per_channel, args.stress_client_test_cases)
+
+ run_test_main(test_settings, gke_settings, stress_server_settings,
+ stress_client_settings)