blob: de4a22877ca52327bb9df04761cefeaae957812d [file] [log] [blame]
Nathaniel Manistacbf21da2016-02-02 22:17:44 +00001#!/usr/bin/env python2.7
Craig Tiller6169d5f2016-03-31 07:46:18 -07002# Copyright 2015, Google Inc.
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -08003# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30"""Run stress test in C++"""
31
siddharthshukla0589e532016-07-07 16:08:01 +020032from __future__ import print_function
33
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080034import argparse
35import atexit
36import dockerjob
37import itertools
38import jobset
39import json
40import multiprocessing
41import os
42import re
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080043import subprocess
44import sys
45import tempfile
46import time
47import uuid
48
49# Docker doesn't clean up after itself, so we do it on exit.
50atexit.register(lambda: subprocess.call(['stty', 'echo']))
51
52ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
53os.chdir(ROOT)
54
55_DEFAULT_SERVER_PORT = 8080
56_DEFAULT_METRICS_PORT = 8081
57_DEFAULT_TEST_CASES = 'empty_unary:20,large_unary:20,client_streaming:20,server_streaming:20,empty_stream:20'
58_DEFAULT_NUM_CHANNELS_PER_SERVER = 5
59_DEFAULT_NUM_STUBS_PER_CHANNEL = 10
60
61# 15 mins default
Sree Kuchibhotla66977602016-01-04 14:34:11 -080062_DEFAULT_TEST_DURATION_SECS = 900
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080063
64class CXXLanguage:
65
66 def __init__(self):
67 self.client_cwd = None
68 self.server_cwd = None
69 self.safename = 'cxx'
70
71 def client_cmd(self, args):
72 return ['bins/opt/stress_test'] + args
73
74 def server_cmd(self, args):
75 return ['bins/opt/interop_server'] + args
76
77 def global_env(self):
78 return {}
79
80 def __str__(self):
81 return 'c++'
82
83
84_LANGUAGES = {'c++': CXXLanguage(),}
85
86# languages supported as cloud_to_cloud servers
87_SERVERS = ['c++']
88
89DOCKER_WORKDIR_ROOT = '/var/local/git/grpc'
90
91
92def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
93 """Wraps given cmdline array to create 'docker run' cmdline from it."""
94 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
95
96 # turn environ into -e docker args
97 if environ:
siddharthshukla0589e532016-07-07 16:08:01 +020098 for k, v in environ.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080099 docker_cmdline += ['-e', '%s=%s' % (k, v)]
100
101 # set working directory
102 workdir = DOCKER_WORKDIR_ROOT
103 if cwd:
104 workdir = os.path.join(workdir, cwd)
105 docker_cmdline += ['-w', workdir]
106
107 docker_cmdline += docker_args + [image] + cmdline
108 return docker_cmdline
109
110
111def bash_login_cmdline(cmdline):
112 """Creates bash -l -c cmdline from args list."""
113 # Use login shell:
114 # * rvm and nvm require it
115 # * makes error messages clearer if executables are missing
116 return ['bash', '-l', '-c', ' '.join(cmdline)]
117
118
119def _job_kill_handler(job):
120 if job._spec.container_name:
121 dockerjob.docker_kill(job._spec.container_name)
122 # When the job times out and we decide to kill it,
123 # we need to wait a before restarting the job
124 # to prevent "container name already in use" error.
125 # TODO(jtattermusch): figure out a cleaner way to to this.
126 time.sleep(2)
127
128
129def cloud_to_cloud_jobspec(language,
130 test_cases,
131 server_addresses,
132 test_duration_secs,
133 num_channels_per_server,
134 num_stubs_per_channel,
135 metrics_port,
136 docker_image=None):
137 """Creates jobspec for cloud-to-cloud interop test"""
138 cmdline = bash_login_cmdline(language.client_cmd([
139 '--test_cases=%s' % test_cases, '--server_addresses=%s' %
140 server_addresses, '--test_duration_secs=%s' % test_duration_secs,
141 '--num_stubs_per_channel=%s' % num_stubs_per_channel,
142 '--num_channels_per_server=%s' % num_channels_per_server,
143 '--metrics_port=%s' % metrics_port
144 ]))
siddharthshukla0589e532016-07-07 16:08:01 +0200145 print(cmdline)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800146 cwd = language.client_cwd
147 environ = language.global_env()
148 if docker_image:
149 container_name = dockerjob.random_name('interop_client_%s' %
150 language.safename)
151 cmdline = docker_run_cmdline(
152 cmdline,
153 image=docker_image,
154 environ=environ,
155 cwd=cwd,
156 docker_args=['--net=host', '--name', container_name])
157 cwd = None
158
159 test_job = jobset.JobSpec(cmdline=cmdline,
160 cwd=cwd,
161 environ=environ,
162 shortname='cloud_to_cloud:%s:%s_server:stress_test' % (
163 language, server_name),
164 timeout_seconds=test_duration_secs * 2,
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800165 flake_retries=0,
166 timeout_retries=0,
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800167 kill_handler=_job_kill_handler)
168 test_job.container_name = container_name
169 return test_job
170
171
172def server_jobspec(language, docker_image, test_duration_secs):
173 """Create jobspec for running a server"""
174 container_name = dockerjob.random_name('interop_server_%s' %
175 language.safename)
176 cmdline = bash_login_cmdline(language.server_cmd(['--port=%s' %
177 _DEFAULT_SERVER_PORT]))
178 environ = language.global_env()
179 docker_cmdline = docker_run_cmdline(
180 cmdline,
181 image=docker_image,
182 cwd=language.server_cwd,
183 environ=environ,
184 docker_args=['-p', str(_DEFAULT_SERVER_PORT), '--name', container_name])
185
186 server_job = jobset.JobSpec(cmdline=docker_cmdline,
187 environ=environ,
188 shortname='interop_server_%s' % language,
189 timeout_seconds=test_duration_secs * 3)
190 server_job.container_name = container_name
191 return server_job
192
193
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800194def build_interop_stress_image_jobspec(language, tag=None):
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800195 """Creates jobspec for building stress test docker image for a language"""
196 if not tag:
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800197 tag = 'grpc_interop_stress_%s:%s' % (language.safename, uuid.uuid4())
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800198 env = {'INTEROP_IMAGE': tag,
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800199 'BASE_NAME': 'grpc_interop_stress_%s' % language.safename}
Jan Tattermusch9835d4b2016-04-29 15:05:05 -0700200 build_job = jobset.JobSpec(cmdline=['tools/run_tests/dockerize/build_interop_stress_image.sh'],
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800201 environ=env,
202 shortname='build_docker_%s' % (language),
203 timeout_seconds=30 * 60)
204 build_job.tag = tag
205 return build_job
206
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800207argp = argparse.ArgumentParser(description='Run stress tests.')
208argp.add_argument('-l',
209 '--language',
210 choices=['all'] + sorted(_LANGUAGES),
211 nargs='+',
212 default=['all'],
213 help='Clients to run.')
214argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
215argp.add_argument(
216 '-s',
217 '--server',
218 choices=['all'] + sorted(_SERVERS),
219 action='append',
220 help='Run cloud_to_cloud servers in a separate docker ' + 'image.',
221 default=[])
222argp.add_argument(
223 '--override_server',
224 action='append',
225 type=lambda kv: kv.split('='),
226 help=
227 'Use servername=HOST:PORT to explicitly specify a server. E.g. '
228 'csharp=localhost:50000',
229 default=[])
230argp.add_argument('--test_duration_secs',
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800231 help='The duration of the test in seconds',
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800232 default=_DEFAULT_TEST_DURATION_SECS)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800233
234args = argp.parse_args()
235
236servers = set(
237 s
238 for s in itertools.chain.from_iterable(_SERVERS if x == 'all' else [x]
239 for x in args.server))
240
241languages = set(_LANGUAGES[l]
242 for l in itertools.chain.from_iterable(_LANGUAGES.iterkeys(
243 ) if x == 'all' else [x] for x in args.language))
244
245docker_images = {}
246# languages for which to build docker images
247languages_to_build = set(
248 _LANGUAGES[k]
249 for k in set([str(l) for l in languages] + [s for s in servers]))
250build_jobs = []
251for l in languages_to_build:
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800252 job = build_interop_stress_image_jobspec(l)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800253 docker_images[str(l)] = job.tag
254 build_jobs.append(job)
255
256if build_jobs:
257 jobset.message('START', 'Building interop docker images.', do_newline=True)
258 num_failures, _ = jobset.run(build_jobs,
259 newline_on_success=True,
260 maxjobs=args.jobs)
261 if num_failures == 0:
262 jobset.message('SUCCESS',
263 'All docker images built successfully.',
264 do_newline=True)
265 else:
266 jobset.message('FAILED',
267 'Failed to build interop docker images.',
268 do_newline=True)
269 for image in docker_images.itervalues():
270 dockerjob.remove_image(image, skip_nonexistent=True)
271 sys.exit(1)
272
273# Start interop servers.
274server_jobs = {}
275server_addresses = {}
276try:
277 for s in servers:
278 lang = str(s)
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800279 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang), args.test_duration_secs)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800280 job = dockerjob.DockerJob(spec)
281 server_jobs[lang] = job
282 server_addresses[lang] = ('localhost',
283 job.mapped_port(_DEFAULT_SERVER_PORT))
284
285 jobs = []
286
287 for server in args.override_server:
288 server_name = server[0]
289 (server_host, server_port) = server[1].split(':')
290 server_addresses[server_name] = (server_host, server_port)
291
siddharthshukla0589e532016-07-07 16:08:01 +0200292 for server_name, server_address in server_addresses.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800293 (server_host, server_port) = server_address
294 for language in languages:
295 test_job = cloud_to_cloud_jobspec(
296 language,
297 _DEFAULT_TEST_CASES,
298 ('%s:%s' % (server_host, server_port)),
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800299 args.test_duration_secs,
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800300 _DEFAULT_NUM_CHANNELS_PER_SERVER,
301 _DEFAULT_NUM_STUBS_PER_CHANNEL,
302 _DEFAULT_METRICS_PORT,
303 docker_image=docker_images.get(str(language)))
304 jobs.append(test_job)
305
306 if not jobs:
siddharthshukla0589e532016-07-07 16:08:01 +0200307 print('No jobs to run.')
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800308 for image in docker_images.itervalues():
309 dockerjob.remove_image(image, skip_nonexistent=True)
310 sys.exit(1)
311
312 num_failures, resultset = jobset.run(jobs,
313 newline_on_success=True,
314 maxjobs=args.jobs)
315 if num_failures:
316 jobset.message('FAILED', 'Some tests failed', do_newline=True)
317 else:
318 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
319
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800320finally:
321 # Check if servers are still running.
siddharthshukla0589e532016-07-07 16:08:01 +0200322 for server, job in server_jobs.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800323 if not job.is_running():
siddharthshukla0589e532016-07-07 16:08:01 +0200324 print('Server "%s" has exited prematurely.' % server)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800325
326 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
327
328 for image in docker_images.itervalues():
siddharthshukla0589e532016-07-07 16:08:01 +0200329 print('Removing docker image %s' % image)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800330 dockerjob.remove_image(image)