blob: a94a615b8863aea920bb943acaed1b77972766a1 [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
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080036import itertools
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080037import json
38import multiprocessing
39import os
40import re
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080041import subprocess
42import sys
43import tempfile
44import time
45import uuid
46
Jan Tattermusch5c79a312016-12-20 11:02:50 +010047import python_utils.dockerjob as dockerjob
48import python_utils.jobset as jobset
49
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080050# Docker doesn't clean up after itself, so we do it on exit.
51atexit.register(lambda: subprocess.call(['stty', 'echo']))
52
53ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
54os.chdir(ROOT)
55
56_DEFAULT_SERVER_PORT = 8080
57_DEFAULT_METRICS_PORT = 8081
58_DEFAULT_TEST_CASES = 'empty_unary:20,large_unary:20,client_streaming:20,server_streaming:20,empty_stream:20'
59_DEFAULT_NUM_CHANNELS_PER_SERVER = 5
60_DEFAULT_NUM_STUBS_PER_CHANNEL = 10
61
62# 15 mins default
Sree Kuchibhotla66977602016-01-04 14:34:11 -080063_DEFAULT_TEST_DURATION_SECS = 900
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -080064
65class CXXLanguage:
66
67 def __init__(self):
68 self.client_cwd = None
69 self.server_cwd = None
70 self.safename = 'cxx'
71
72 def client_cmd(self, args):
73 return ['bins/opt/stress_test'] + args
74
75 def server_cmd(self, args):
76 return ['bins/opt/interop_server'] + args
77
78 def global_env(self):
79 return {}
80
81 def __str__(self):
82 return 'c++'
83
84
85_LANGUAGES = {'c++': CXXLanguage(),}
86
87# languages supported as cloud_to_cloud servers
88_SERVERS = ['c++']
89
90DOCKER_WORKDIR_ROOT = '/var/local/git/grpc'
91
92
93def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
94 """Wraps given cmdline array to create 'docker run' cmdline from it."""
95 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
96
97 # turn environ into -e docker args
98 if environ:
siddharthshukla0589e532016-07-07 16:08:01 +020099 for k, v in environ.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800100 docker_cmdline += ['-e', '%s=%s' % (k, v)]
101
102 # set working directory
103 workdir = DOCKER_WORKDIR_ROOT
104 if cwd:
105 workdir = os.path.join(workdir, cwd)
106 docker_cmdline += ['-w', workdir]
107
108 docker_cmdline += docker_args + [image] + cmdline
109 return docker_cmdline
110
111
112def bash_login_cmdline(cmdline):
113 """Creates bash -l -c cmdline from args list."""
114 # Use login shell:
115 # * rvm and nvm require it
116 # * makes error messages clearer if executables are missing
117 return ['bash', '-l', '-c', ' '.join(cmdline)]
118
119
120def _job_kill_handler(job):
121 if job._spec.container_name:
122 dockerjob.docker_kill(job._spec.container_name)
123 # When the job times out and we decide to kill it,
124 # we need to wait a before restarting the job
125 # to prevent "container name already in use" error.
126 # TODO(jtattermusch): figure out a cleaner way to to this.
127 time.sleep(2)
128
129
130def cloud_to_cloud_jobspec(language,
131 test_cases,
132 server_addresses,
133 test_duration_secs,
134 num_channels_per_server,
135 num_stubs_per_channel,
136 metrics_port,
137 docker_image=None):
138 """Creates jobspec for cloud-to-cloud interop test"""
139 cmdline = bash_login_cmdline(language.client_cmd([
140 '--test_cases=%s' % test_cases, '--server_addresses=%s' %
141 server_addresses, '--test_duration_secs=%s' % test_duration_secs,
142 '--num_stubs_per_channel=%s' % num_stubs_per_channel,
143 '--num_channels_per_server=%s' % num_channels_per_server,
144 '--metrics_port=%s' % metrics_port
145 ]))
siddharthshukla0589e532016-07-07 16:08:01 +0200146 print(cmdline)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800147 cwd = language.client_cwd
148 environ = language.global_env()
149 if docker_image:
150 container_name = dockerjob.random_name('interop_client_%s' %
151 language.safename)
152 cmdline = docker_run_cmdline(
153 cmdline,
154 image=docker_image,
155 environ=environ,
156 cwd=cwd,
157 docker_args=['--net=host', '--name', container_name])
158 cwd = None
159
160 test_job = jobset.JobSpec(cmdline=cmdline,
161 cwd=cwd,
162 environ=environ,
163 shortname='cloud_to_cloud:%s:%s_server:stress_test' % (
164 language, server_name),
165 timeout_seconds=test_duration_secs * 2,
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800166 flake_retries=0,
167 timeout_retries=0,
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800168 kill_handler=_job_kill_handler)
169 test_job.container_name = container_name
170 return test_job
171
172
173def server_jobspec(language, docker_image, test_duration_secs):
174 """Create jobspec for running a server"""
175 container_name = dockerjob.random_name('interop_server_%s' %
176 language.safename)
177 cmdline = bash_login_cmdline(language.server_cmd(['--port=%s' %
178 _DEFAULT_SERVER_PORT]))
179 environ = language.global_env()
180 docker_cmdline = docker_run_cmdline(
181 cmdline,
182 image=docker_image,
183 cwd=language.server_cwd,
184 environ=environ,
185 docker_args=['-p', str(_DEFAULT_SERVER_PORT), '--name', container_name])
186
187 server_job = jobset.JobSpec(cmdline=docker_cmdline,
188 environ=environ,
189 shortname='interop_server_%s' % language,
190 timeout_seconds=test_duration_secs * 3)
191 server_job.container_name = container_name
192 return server_job
193
194
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800195def build_interop_stress_image_jobspec(language, tag=None):
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800196 """Creates jobspec for building stress test docker image for a language"""
197 if not tag:
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800198 tag = 'grpc_interop_stress_%s:%s' % (language.safename, uuid.uuid4())
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800199 env = {'INTEROP_IMAGE': tag,
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800200 'BASE_NAME': 'grpc_interop_stress_%s' % language.safename}
Jan Tattermusch9835d4b2016-04-29 15:05:05 -0700201 build_job = jobset.JobSpec(cmdline=['tools/run_tests/dockerize/build_interop_stress_image.sh'],
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800202 environ=env,
203 shortname='build_docker_%s' % (language),
204 timeout_seconds=30 * 60)
205 build_job.tag = tag
206 return build_job
207
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800208argp = argparse.ArgumentParser(description='Run stress tests.')
209argp.add_argument('-l',
210 '--language',
211 choices=['all'] + sorted(_LANGUAGES),
212 nargs='+',
213 default=['all'],
214 help='Clients to run.')
215argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
216argp.add_argument(
217 '-s',
218 '--server',
219 choices=['all'] + sorted(_SERVERS),
220 action='append',
221 help='Run cloud_to_cloud servers in a separate docker ' + 'image.',
222 default=[])
223argp.add_argument(
224 '--override_server',
225 action='append',
226 type=lambda kv: kv.split('='),
227 help=
228 'Use servername=HOST:PORT to explicitly specify a server. E.g. '
229 'csharp=localhost:50000',
230 default=[])
231argp.add_argument('--test_duration_secs',
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800232 help='The duration of the test in seconds',
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800233 default=_DEFAULT_TEST_DURATION_SECS)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800234
235args = argp.parse_args()
236
237servers = set(
238 s
239 for s in itertools.chain.from_iterable(_SERVERS if x == 'all' else [x]
240 for x in args.server))
241
242languages = set(_LANGUAGES[l]
243 for l in itertools.chain.from_iterable(_LANGUAGES.iterkeys(
244 ) if x == 'all' else [x] for x in args.language))
245
246docker_images = {}
247# languages for which to build docker images
248languages_to_build = set(
249 _LANGUAGES[k]
250 for k in set([str(l) for l in languages] + [s for s in servers]))
251build_jobs = []
252for l in languages_to_build:
Sree Kuchibhotlae3717422016-01-04 10:14:38 -0800253 job = build_interop_stress_image_jobspec(l)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800254 docker_images[str(l)] = job.tag
255 build_jobs.append(job)
256
257if build_jobs:
258 jobset.message('START', 'Building interop docker images.', do_newline=True)
259 num_failures, _ = jobset.run(build_jobs,
260 newline_on_success=True,
261 maxjobs=args.jobs)
262 if num_failures == 0:
263 jobset.message('SUCCESS',
264 'All docker images built successfully.',
265 do_newline=True)
266 else:
267 jobset.message('FAILED',
268 'Failed to build interop docker images.',
269 do_newline=True)
270 for image in docker_images.itervalues():
271 dockerjob.remove_image(image, skip_nonexistent=True)
272 sys.exit(1)
273
274# Start interop servers.
275server_jobs = {}
276server_addresses = {}
277try:
278 for s in servers:
279 lang = str(s)
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800280 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang), args.test_duration_secs)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800281 job = dockerjob.DockerJob(spec)
282 server_jobs[lang] = job
283 server_addresses[lang] = ('localhost',
284 job.mapped_port(_DEFAULT_SERVER_PORT))
285
286 jobs = []
287
288 for server in args.override_server:
289 server_name = server[0]
290 (server_host, server_port) = server[1].split(':')
291 server_addresses[server_name] = (server_host, server_port)
292
siddharthshukla0589e532016-07-07 16:08:01 +0200293 for server_name, server_address in server_addresses.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800294 (server_host, server_port) = server_address
295 for language in languages:
296 test_job = cloud_to_cloud_jobspec(
297 language,
298 _DEFAULT_TEST_CASES,
299 ('%s:%s' % (server_host, server_port)),
Sree Kuchibhotlaca8e3d72016-01-04 09:39:05 -0800300 args.test_duration_secs,
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800301 _DEFAULT_NUM_CHANNELS_PER_SERVER,
302 _DEFAULT_NUM_STUBS_PER_CHANNEL,
303 _DEFAULT_METRICS_PORT,
304 docker_image=docker_images.get(str(language)))
305 jobs.append(test_job)
306
307 if not jobs:
siddharthshukla0589e532016-07-07 16:08:01 +0200308 print('No jobs to run.')
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800309 for image in docker_images.itervalues():
310 dockerjob.remove_image(image, skip_nonexistent=True)
311 sys.exit(1)
312
313 num_failures, resultset = jobset.run(jobs,
314 newline_on_success=True,
315 maxjobs=args.jobs)
316 if num_failures:
317 jobset.message('FAILED', 'Some tests failed', do_newline=True)
318 else:
319 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
320
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800321finally:
322 # Check if servers are still running.
siddharthshukla0589e532016-07-07 16:08:01 +0200323 for server, job in server_jobs.items():
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800324 if not job.is_running():
siddharthshukla0589e532016-07-07 16:08:01 +0200325 print('Server "%s" has exited prematurely.' % server)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800326
327 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
328
329 for image in docker_images.itervalues():
siddharthshukla0589e532016-07-07 16:08:01 +0200330 print('Removing docker image %s' % image)
Sree Kuchibhotla1b38bb42015-12-14 17:22:38 -0800331 dockerjob.remove_image(image)