blob: dea33f7ab20a86a02ee8d3c05670b3740b46ed00 [file] [log] [blame]
Jan Tattermusch320bd612015-09-15 12:44:35 -07001#!/usr/bin/env python
2# Copyright 2015, Google Inc.
3# 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
31"""Run interop (cross-language) tests in parallel."""
32
33import argparse
Jan Tattermusch91ad0182015-10-01 09:22:03 -070034import dockerjob
Jan Tattermusch320bd612015-09-15 12:44:35 -070035import itertools
36import xml.etree.cElementTree as ET
37import jobset
Jan Tattermusch210a0ea2015-10-02 15:05:36 -070038import multiprocessing
Jan Tattermusch8266c672015-09-17 09:18:03 -070039import os
40import subprocess
41import sys
Jan Tattermusch91ad0182015-10-01 09:22:03 -070042import tempfile
Jan Tattermusch8266c672015-09-17 09:18:03 -070043import time
Jan Tattermusch91ad0182015-10-01 09:22:03 -070044import uuid
Jan Tattermusch320bd612015-09-15 12:44:35 -070045
Jan Tattermusch91ad0182015-10-01 09:22:03 -070046ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
47os.chdir(ROOT)
48
49_DEFAULT_SERVER_PORT=8080
Jan Tattermuschf49936a2015-09-16 15:44:26 -070050
51_CLOUD_TO_PROD_BASE_ARGS = [
52 '--server_host_override=grpc-test.sandbox.google.com',
53 '--server_host=grpc-test.sandbox.google.com',
54 '--server_port=443']
55
Jan Tattermusch8266c672015-09-17 09:18:03 -070056_CLOUD_TO_CLOUD_BASE_ARGS = [
57 '--server_host_override=foo.test.google.fr']
58
Jan Tattermuschf49936a2015-09-16 15:44:26 -070059# TOOD(jtattermusch) wrapped languages use this variable for location
60# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
61# supported by C core SslCredentials instead.
62_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
63
Jan Tattermuschcc1bde72015-10-05 12:47:45 -070064# TODO(jtattermusch) unify usage of --use_tls and --use_tls=true
Jan Tattermuschcc1bde72015-10-05 12:47:45 -070065# TODO(jtattermusch) go uses --tls_ca_file instead of --use_test_ca
Jan Tattermuschf49936a2015-09-16 15:44:26 -070066
Jan Tattermusch8266c672015-09-17 09:18:03 -070067
Jan Tattermuschf49936a2015-09-16 15:44:26 -070068class CXXLanguage:
69
70 def __init__(self):
71 self.client_cmdline_base = ['bins/opt/interop_client']
72 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -070073 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -070074 self.safename = 'cxx'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070075
76 def cloud_to_prod_args(self):
77 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070078 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -070079
Jan Tattermusch8266c672015-09-17 09:18:03 -070080 def cloud_to_cloud_args(self):
81 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070082 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -070083
Jan Tattermuschf49936a2015-09-16 15:44:26 -070084 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070085 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070086
Jan Tattermusch91ad0182015-10-01 09:22:03 -070087 def server_args(self):
Jan Tattermusch28bf5592015-10-02 13:50:24 -070088 return ['bins/opt/interop_server', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070089
Jan Tattermuschf49936a2015-09-16 15:44:26 -070090 def __str__(self):
91 return 'c++'
92
93
94class CSharpLanguage:
95
96 def __init__(self):
97 self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
98 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070099 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700100 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700101
102 def cloud_to_prod_args(self):
103 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700104 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700105
Jan Tattermusch8266c672015-09-17 09:18:03 -0700106 def cloud_to_cloud_args(self):
107 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700108 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -0700109
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700110 def cloud_to_prod_env(self):
111 return _SSL_CERT_ENV
112
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700113 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -0700114 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700115
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700116 def __str__(self):
117 return 'csharp'
118
119
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700120class JavaLanguage:
121
122 def __init__(self):
123 self.client_cmdline_base = ['./run-test-client.sh']
124 self.client_cwd = '../grpc-java'
125 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700126 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700127
128 def cloud_to_prod_args(self):
129 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
130 ['--use_tls=true'])
131
132 def cloud_to_cloud_args(self):
133 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
134 ['--use_tls=true', '--use_test_ca=true'])
135
136 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700137 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700138
139 def server_args(self):
140 return ['./run-test-server.sh', '--use_tls=true']
141
142 def __str__(self):
143 return 'java'
144
145
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700146class GoLanguage:
147
148 def __init__(self):
149 self.client_cmdline_base = ['go', 'run', 'client.go']
150 # TODO: this relies on running inside docker
151 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
152 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700153 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700154
155 def cloud_to_prod_args(self):
156 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
157 ['--use_tls=true', '--tls_ca_file=""'])
158
159 def cloud_to_cloud_args(self):
160 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
161 ['--use_tls=true'])
162
163 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700164 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700165
166 def server_args(self):
167 return ['go', 'run', 'server.go', '--use_tls=true']
168
169 def __str__(self):
170 return 'go'
171
172
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700173class NodeLanguage:
174
175 def __init__(self):
176 self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
177 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700178 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700179 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700180
181 def cloud_to_prod_args(self):
182 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
183 ['--use_tls=true'])
184
Jan Tattermusch8266c672015-09-17 09:18:03 -0700185 def cloud_to_cloud_args(self):
186 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
187 ['--use_tls=true', '--use_test_ca=true'])
188
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700189 def cloud_to_prod_env(self):
190 return _SSL_CERT_ENV
191
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700192 def server_args(self):
193 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
194
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700195 def __str__(self):
196 return 'node'
197
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700198
199class PHPLanguage:
200
201 def __init__(self):
202 self.client_cmdline_base = ['src/php/bin/interop_client.sh']
203 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700204 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700205
206 def cloud_to_prod_args(self):
207 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
208 ['--use_tls'])
209
Jan Tattermusch8266c672015-09-17 09:18:03 -0700210 def cloud_to_cloud_args(self):
211 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
212 ['--use_tls', '--use_test_ca'])
213
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700214 def cloud_to_prod_env(self):
215 return _SSL_CERT_ENV
216
217 def __str__(self):
218 return 'php'
219
220
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700221class RubyLanguage:
222
223 def __init__(self):
224 self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
225 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700226 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700227 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700228
229 def cloud_to_prod_args(self):
230 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
231 ['--use_tls'])
232
Jan Tattermusch8266c672015-09-17 09:18:03 -0700233 def cloud_to_cloud_args(self):
234 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
235 ['--use_tls', '--use_test_ca'])
236
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700237 def cloud_to_prod_env(self):
238 return _SSL_CERT_ENV
239
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700240 def server_args(self):
241 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls']
242
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700243 def __str__(self):
244 return 'ruby'
245
246
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700247# TODO(jtattermusch): python once we get it working
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700248_LANGUAGES = {
249 'c++' : CXXLanguage(),
250 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700251 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700252 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700253 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700254 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700255 'ruby' : RubyLanguage(),
256}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700257
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700258# languages supported as cloud_to_cloud servers
Jan Tattermuschb1dec722015-10-06 11:46:01 -0700259_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700260
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700261# TODO(jtattermusch): add empty_stream once PHP starts supporting it.
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700262# TODO(jtattermusch): add timeout_on_sleeping_server once java starts supporting it.
Jan Tattermusch320bd612015-09-15 12:44:35 -0700263# TODO(jtattermusch): add support for auth tests.
264_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700265 'client_streaming', 'server_streaming',
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700266 'cancel_after_begin', 'cancel_after_first_response']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700267
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700268_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
269 'oauth2_auth_token', 'per_rpc_creds']
270
Jan Tattermusch8266c672015-09-17 09:18:03 -0700271
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700272def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
273 """Wraps given cmdline array to create 'docker run' cmdline from it."""
274 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
275
276 # turn environ into -e docker args
277 if environ:
278 for k,v in environ.iteritems():
279 docker_cmdline += ['-e', '%s=%s' % (k,v)]
280
281 # set working directory
282 workdir = '/var/local/git/grpc'
283 if cwd:
284 workdir = os.path.join(workdir, cwd)
285 docker_cmdline += ['-w', workdir]
286
287 docker_cmdline += docker_args + [image] + cmdline
288 return docker_cmdline
289
290
291def bash_login_cmdline(cmdline):
292 """Creates bash -l -c cmdline from args list."""
293 # Use login shell:
294 # * rvm and nvm require it
295 # * makes error messages clearer if executables are missing
296 return ['bash', '-l', '-c', ' '.join(cmdline)]
297
298
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700299def add_auth_options(language, test_case, cmdline, env):
300 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
301
302 language = str(language)
303 cmdline = list(cmdline)
304 env = env.copy()
305
306 # TODO(jtattermusch): this file path only works inside docker
307 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
308 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
309 key_file_arg = '--service_account_key_file=%s' % key_filepath
310 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
311
312 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
313 if language in ['csharp', 'node', 'php', 'ruby']:
314 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
315 else:
316 cmdline += [key_file_arg]
317
318 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
319 cmdline += [oauth_scope_arg]
320
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700321 if test_case == 'oauth2_auth_token' and language == 'c++':
322 # C++ oauth2 test uses GCE creds and thus needs to know the default account
323 cmdline += [default_account_arg]
324
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700325 if test_case == 'compute_engine_creds':
326 cmdline += [oauth_scope_arg, default_account_arg]
327
328 return (cmdline, env)
329
330
Jan Tattermusche2686282015-10-08 16:27:07 -0700331def _job_kill_handler(job):
332 if job._spec.container_name:
333 dockerjob.docker_kill(job._spec.container_name)
334
335
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700336def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700337 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700338 cmdline = language.cloud_to_prod_args() + ['--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700339 cwd = language.client_cwd
340 environ = language.cloud_to_prod_env()
Jan Tattermusche2686282015-10-08 16:27:07 -0700341 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700342 if auth:
343 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
344 cmdline = bash_login_cmdline(cmdline)
345
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700346 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700347 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700348 cmdline = docker_run_cmdline(cmdline,
349 image=docker_image,
350 cwd=cwd,
351 environ=environ,
352 docker_args=['--net=host',
353 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700354 cwd = None
355 environ = None
356
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700357 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700358 test_job = jobset.JobSpec(
359 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700360 cwd=cwd,
361 environ=environ,
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700362 shortname="%s:%s:%s" % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700363 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700364 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700365 timeout_retries=2 if args.allow_flakes else 0,
366 kill_handler=_job_kill_handler)
367 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700368 return test_job
369
Jan Tattermusch8266c672015-09-17 09:18:03 -0700370
371def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700372 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700373 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700374 cmdline = bash_login_cmdline(language.cloud_to_cloud_args() +
375 ['--test_case=%s' % test_case,
376 '--server_host=%s' % server_host,
377 '--server_port=%s' % server_port ])
378 cwd = language.client_cwd
379 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700380 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700381 cmdline = docker_run_cmdline(cmdline,
382 image=docker_image,
383 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700384 docker_args=['--net=host',
385 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700386 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700387
Jan Tattermusch8266c672015-09-17 09:18:03 -0700388 test_job = jobset.JobSpec(
389 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700390 cwd=cwd,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700391 shortname="cloud_to_cloud:%s:%s_server:%s" % (language, server_name,
392 test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700393 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700394 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700395 timeout_retries=2 if args.allow_flakes else 0,
396 kill_handler=_job_kill_handler)
397 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700398 return test_job
399
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700400
401def server_jobspec(language, docker_image):
402 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700403 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700404 cmdline = bash_login_cmdline(language.server_args() +
405 ['--port=%s' % _DEFAULT_SERVER_PORT])
406 docker_cmdline = docker_run_cmdline(cmdline,
407 image=docker_image,
408 cwd=language.server_cwd,
409 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700410 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700411 server_job = jobset.JobSpec(
412 cmdline=docker_cmdline,
Jan Tattermusche2686282015-10-08 16:27:07 -0700413 shortname="interop_server_%s" % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700414 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700415 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700416 return server_job
417
418
419def build_interop_image_jobspec(language, tag=None):
420 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700421 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700422 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
423 env = {'INTEROP_IMAGE': tag,
424 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700425 if not args.travis:
426 env['TTY_FLAG'] = '-t'
427 build_job = jobset.JobSpec(
428 cmdline=['tools/jenkins/build_interop_image.sh'],
429 environ=env,
430 shortname="build_docker_%s" % (language),
431 timeout_seconds=30*60)
432 build_job.tag = tag
433 return build_job
434
435
Jan Tattermusch320bd612015-09-15 12:44:35 -0700436argp = argparse.ArgumentParser(description='Run interop tests.')
437argp.add_argument('-l', '--language',
438 choices=['all'] + sorted(_LANGUAGES),
439 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700440 default=['all'],
441 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700442argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700443argp.add_argument('--cloud_to_prod',
444 default=False,
445 action='store_const',
446 const=True,
447 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700448argp.add_argument('--cloud_to_prod_auth',
449 default=False,
450 action='store_const',
451 const=True,
452 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700453argp.add_argument('-s', '--server',
454 choices=['all'] + sorted(_SERVERS),
455 action='append',
456 help='Run cloud_to_cloud servers in a separate docker ' +
457 'image. Servers can only be started automatically if ' +
458 '--use_docker option is enabled.',
459 default=[])
460argp.add_argument('--override_server',
461 action='append',
462 type=lambda kv: kv.split("="),
463 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
464 default=[])
465argp.add_argument('-t', '--travis',
466 default=False,
467 action='store_const',
468 const=True)
469argp.add_argument('--use_docker',
470 default=False,
471 action='store_const',
472 const=True,
473 help='Run all the interop tests under docker. That provides ' +
474 'additional isolation and prevents the need to install ' +
475 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700476argp.add_argument('--allow_flakes',
477 default=False,
478 action='store_const',
479 const=True,
480 help="Allow flaky tests to show as passing (re-runs failed tests up to five times)")
Jan Tattermusch320bd612015-09-15 12:44:35 -0700481args = argp.parse_args()
482
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700483servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700484 if x == 'all' else [x]
485 for x in args.server))
486
487if args.use_docker:
488 if not args.travis:
489 print 'Seen --use_docker flag, will run interop tests under docker.'
490 print
491 print 'IMPORTANT: The changes you are testing need to be locally committed'
492 print 'because only the committed changes in the current branch will be'
493 print 'copied to the docker environment.'
494 time.sleep(5)
495
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700496if not args.use_docker and servers:
497 print "Running interop servers is only supported with --use_docker option enabled."
498 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700499
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700500languages = set(_LANGUAGES[l]
501 for l in itertools.chain.from_iterable(
502 _LANGUAGES.iterkeys() if x == 'all' else [x]
503 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700504
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700505docker_images={}
506if args.use_docker:
507 # languages for which to build docker images
508 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
509 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700510
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700511 build_jobs = []
512 for l in languages_to_build:
513 job = build_interop_image_jobspec(l)
514 docker_images[str(l)] = job.tag
515 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700516
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700517 if build_jobs:
518 jobset.message('START', 'Building interop docker images.', do_newline=True)
519 if jobset.run(build_jobs, newline_on_success=True, maxjobs=args.jobs):
520 jobset.message('SUCCESS', 'All docker images built successfully.', do_newline=True)
521 else:
522 jobset.message('FAILED', 'Failed to build interop docker images.', do_newline=True)
523 for image in docker_images.itervalues():
524 dockerjob.remove_image(image, skip_nonexistent=True)
525 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700526
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700527# Start interop servers.
528server_jobs={}
529server_addresses={}
530try:
531 for s in servers:
532 lang = str(s)
533 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
534 job = dockerjob.DockerJob(spec)
535 server_jobs[lang] = job
536 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700537
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700538
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700539 jobs = []
540 if args.cloud_to_prod:
541 for language in languages:
542 for test_case in _TEST_CASES:
543 test_job = cloud_to_prod_jobspec(language, test_case,
544 docker_image=docker_images.get(str(language)))
545 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700546
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700547 if args.cloud_to_prod_auth:
548 for language in languages:
549 for test_case in _AUTH_TEST_CASES:
550 test_job = cloud_to_prod_jobspec(language, test_case,
551 docker_image=docker_images.get(str(language)),
552 auth=True)
553 jobs.append(test_job)
554
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700555 for server in args.override_server:
556 server_name = server[0]
557 (server_host, server_port) = server[1].split(':')
558 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700559
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700560 for server_name, server_address in server_addresses.iteritems():
561 (server_host, server_port) = server_address
562 for language in languages:
563 for test_case in _TEST_CASES:
564 test_job = cloud_to_cloud_jobspec(language,
565 test_case,
566 server_name,
567 server_host,
568 server_port,
569 docker_image=docker_images.get(str(language)))
570 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700571
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700572 if not jobs:
573 print "No jobs to run."
574 for image in docker_images.itervalues():
575 dockerjob.remove_image(image, skip_nonexistent=True)
576 sys.exit(1)
577
578 root = ET.Element('testsuites')
579 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
580
581 if jobset.run(jobs, newline_on_success=True, maxjobs=args.jobs, xml_report=testsuite):
582 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
583 else:
584 jobset.message('FAILED', 'Some tests failed', do_newline=True)
585
586 tree = ET.ElementTree(root)
587 tree.write('report.xml', encoding='UTF-8')
588
589finally:
590 # Check if servers are still running.
591 for server, job in server_jobs.iteritems():
592 if not job.is_running():
593 print 'Server "%s" has exited prematurely.' % server
594
595 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
596
597 for image in docker_images.itervalues():
598 print 'Removing docker image %s' % image
599 dockerjob.remove_image(image)