blob: ace7a5e55de20fee4471555078cf293cd5b46cb3 [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
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070051# TOOD(jtattermusch) wrapped languages use this variable for location
Jan Tattermuschf49936a2015-09-16 15:44:26 -070052# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
53# supported by C core SslCredentials instead.
54_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
55
Jan Tattermusch8266c672015-09-17 09:18:03 -070056
Jan Tattermuschf49936a2015-09-16 15:44:26 -070057class CXXLanguage:
58
59 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -070060 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -070061 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -070062 self.safename = 'cxx'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070063
Jan Tattermuschc8b94412015-10-21 17:57:28 -070064 def client_args(self):
65 return ['bins/opt/interop_client']
Jan Tattermusch8266c672015-09-17 09:18:03 -070066
Jan Tattermuschf49936a2015-09-16 15:44:26 -070067 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070068 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070069
Jan Tattermusch91ad0182015-10-01 09:22:03 -070070 def server_args(self):
Jan Tattermusch28bf5592015-10-02 13:50:24 -070071 return ['bins/opt/interop_server', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070072
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070073 def global_env(self):
74 return {}
75
Jan Tattermuschf49936a2015-09-16 15:44:26 -070076 def __str__(self):
77 return 'c++'
78
79
80class CSharpLanguage:
81
82 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -070083 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070084 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -070085 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -070086
Jan Tattermuschc8b94412015-10-21 17:57:28 -070087 def client_args(self):
88 return ['mono', 'Grpc.IntegrationTesting.Client.exe']
Jan Tattermusch8266c672015-09-17 09:18:03 -070089
Jan Tattermuschf49936a2015-09-16 15:44:26 -070090 def cloud_to_prod_env(self):
91 return _SSL_CERT_ENV
92
Jan Tattermusch91ad0182015-10-01 09:22:03 -070093 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -070094 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070095
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070096 def global_env(self):
97 return {}
98
Jan Tattermuschf49936a2015-09-16 15:44:26 -070099 def __str__(self):
100 return 'csharp'
101
102
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700103class JavaLanguage:
104
105 def __init__(self):
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700106 self.client_cwd = '../grpc-java'
107 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700108 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700109
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700110 def client_args(self):
111 return ['./run-test-client.sh']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700112
113 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700114 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700115
116 def server_args(self):
117 return ['./run-test-server.sh', '--use_tls=true']
118
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700119 def global_env(self):
120 return {}
121
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700122 def __str__(self):
123 return 'java'
124
125
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700126class GoLanguage:
127
128 def __init__(self):
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700129 # TODO: this relies on running inside docker
130 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
131 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700132 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700133
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700134 def client_args(self):
135 return ['go', 'run', 'client.go']
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700136
137 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700138 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700139
140 def server_args(self):
141 return ['go', 'run', 'server.go', '--use_tls=true']
142
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700143 def global_env(self):
144 return {}
145
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700146 def __str__(self):
147 return 'go'
148
149
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700150class NodeLanguage:
151
152 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700153 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700154 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700155 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700156
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700157 def client_args(self):
158 return ['node', 'src/node/interop/interop_client.js']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700159
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700160 def cloud_to_prod_env(self):
161 return _SSL_CERT_ENV
162
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700163 def server_args(self):
164 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
165
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700166 def global_env(self):
167 return {}
168
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700169 def __str__(self):
170 return 'node'
171
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700172
173class PHPLanguage:
174
175 def __init__(self):
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700176 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700177 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700178
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700179 def client_args(self):
180 return ['src/php/bin/interop_client.sh']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700181
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700182 def cloud_to_prod_env(self):
183 return _SSL_CERT_ENV
184
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700185 def global_env(self):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700186 return {}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700187
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700188 def __str__(self):
189 return 'php'
190
191
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700192class RubyLanguage:
193
194 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700195 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700196 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700197 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700198
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700199 def client_args(self):
200 return ['ruby', 'src/ruby/bin/interop/interop_client.rb']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700201
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700202 def cloud_to_prod_env(self):
203 return _SSL_CERT_ENV
204
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700205 def server_args(self):
Jan Tattermusch73b3eea2015-10-15 17:52:06 -0700206 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700207
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700208 def global_env(self):
209 return {}
210
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700211 def __str__(self):
212 return 'ruby'
213
214
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700215class PythonLanguage:
216
217 def __init__(self):
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700218 self.client_cwd = None
219 self.server_cwd = None
220 self.safename = str(self)
221
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700222 def client_args(self):
223 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700224
225 def cloud_to_prod_env(self):
226 return _SSL_CERT_ENV
227
228 def server_args(self):
Jan Tattermusch785efd42015-10-15 17:20:22 -0700229 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls=true']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700230
231 def global_env(self):
232 return {'LD_LIBRARY_PATH': 'libs/opt'}
233
234 def __str__(self):
235 return 'python'
236
237
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700238_LANGUAGES = {
239 'c++' : CXXLanguage(),
240 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700241 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700242 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700243 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700244 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700245 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700246 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700247}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700248
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700249# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700250_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700251
Jan Tattermusch320bd612015-09-15 12:44:35 -0700252_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700253 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700254 'cancel_after_begin', 'cancel_after_first_response',
255 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700256
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700257_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
258 'oauth2_auth_token', 'per_rpc_creds']
259
Jan Tattermusch8266c672015-09-17 09:18:03 -0700260
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700261def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
262 """Wraps given cmdline array to create 'docker run' cmdline from it."""
263 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
264
265 # turn environ into -e docker args
266 if environ:
267 for k,v in environ.iteritems():
268 docker_cmdline += ['-e', '%s=%s' % (k,v)]
269
270 # set working directory
271 workdir = '/var/local/git/grpc'
272 if cwd:
273 workdir = os.path.join(workdir, cwd)
274 docker_cmdline += ['-w', workdir]
275
276 docker_cmdline += docker_args + [image] + cmdline
277 return docker_cmdline
278
279
280def bash_login_cmdline(cmdline):
281 """Creates bash -l -c cmdline from args list."""
282 # Use login shell:
283 # * rvm and nvm require it
284 # * makes error messages clearer if executables are missing
285 return ['bash', '-l', '-c', ' '.join(cmdline)]
286
287
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700288def add_auth_options(language, test_case, cmdline, env):
289 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
290
291 language = str(language)
292 cmdline = list(cmdline)
293 env = env.copy()
294
295 # TODO(jtattermusch): this file path only works inside docker
296 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
297 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
298 key_file_arg = '--service_account_key_file=%s' % key_filepath
299 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
300
301 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700302 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700303 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
304 else:
305 cmdline += [key_file_arg]
306
307 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
308 cmdline += [oauth_scope_arg]
309
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700310 if test_case == 'oauth2_auth_token' and language == 'c++':
311 # C++ oauth2 test uses GCE creds and thus needs to know the default account
312 cmdline += [default_account_arg]
313
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700314 if test_case == 'compute_engine_creds':
315 cmdline += [oauth_scope_arg, default_account_arg]
316
317 return (cmdline, env)
318
319
Jan Tattermusche2686282015-10-08 16:27:07 -0700320def _job_kill_handler(job):
321 if job._spec.container_name:
322 dockerjob.docker_kill(job._spec.container_name)
323
324
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700325def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700326 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700327 cmdline = language.client_args() + [
328 '--server_host_override=grpc-test.sandbox.google.com',
329 '--server_host=grpc-test.sandbox.google.com',
330 '--server_port=443',
331 '--use_tls=true',
332 '--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700333 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700334 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700335 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700336 if auth:
337 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
338 cmdline = bash_login_cmdline(cmdline)
339
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700340 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700341 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700342 cmdline = docker_run_cmdline(cmdline,
343 image=docker_image,
344 cwd=cwd,
345 environ=environ,
346 docker_args=['--net=host',
347 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700348 cwd = None
349 environ = None
350
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700351 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700352 test_job = jobset.JobSpec(
353 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700354 cwd=cwd,
355 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700356 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700357 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700358 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700359 timeout_retries=2 if args.allow_flakes else 0,
360 kill_handler=_job_kill_handler)
361 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700362 return test_job
363
Jan Tattermusch8266c672015-09-17 09:18:03 -0700364
365def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700366 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700367 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700368 cmdline = bash_login_cmdline(language.client_args() +
369 ['--server_host_override=foo.test.google.fr',
370 '--use_tls=true',
371 '--use_test_ca=true',
372 '--test_case=%s' % test_case,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700373 '--server_host=%s' % server_host,
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700374 '--server_port=%s' % server_port])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700375 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700376 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700377 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700378 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700379 cmdline = docker_run_cmdline(cmdline,
380 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700381 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700382 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700383 docker_args=['--net=host',
384 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700385 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700386
Jan Tattermusch8266c672015-09-17 09:18:03 -0700387 test_job = jobset.JobSpec(
388 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700389 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700390 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700391 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700392 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])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700406 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700407 docker_cmdline = docker_run_cmdline(cmdline,
408 image=docker_image,
409 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700410 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700411 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700412 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700413 server_job = jobset.JobSpec(
414 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700415 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700416 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700417 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700418 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700419 return server_job
420
421
422def build_interop_image_jobspec(language, tag=None):
423 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700424 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700425 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700426 env = {'INTEROP_IMAGE': tag,
427 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700428 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700429 env['TTY_FLAG'] = '-t'
430 # This env variable is used to get around the github rate limit
431 # error when running the PHP `composer install` command
432 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700433 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700434 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700435 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700436 build_job = jobset.JobSpec(
437 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700438 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700439 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700440 timeout_seconds=30*60)
441 build_job.tag = tag
442 return build_job
443
444
Adele Zhoue4c35612015-10-16 15:34:23 -0700445# TODO(adelez): Use mako template.
446def fill_one_test_result(shortname, resultset, html_str):
447 if shortname in resultset:
448 result = resultset[shortname]
449 if result.state == 'PASSED':
450 html_str = '%s<td bgcolor=\"green\">PASS</td>\n' % html_str
451 else:
452 tooltip = ''
453 if result.returncode > 0 or result.message:
454 if result.returncode > 0:
455 tooltip = 'returncode: %d ' % result.returncode
456 if result.message:
457 tooltip = '%smessage: %s' % (tooltip, result.message)
458 if result.state == 'FAILED':
459 html_str = '%s<td bgcolor=\"red\">' % html_str
460 if tooltip:
461 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
462 'data-placement=\"auto\" title=\"%s\">FAIL</a></td>\n' %
463 (html_str, tooltip))
464 else:
465 html_str = '%sFAIL</td>\n' % html_str
466 elif result.state == 'TIMEOUT':
467 html_str = '%s<td bgcolor=\"yellow\">' % html_str
468 if tooltip:
469 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
470 'data-placement=\"auto\" title=\"%s\">TIMEOUT</a></td>\n'
471 % (html_str, tooltip))
472 else:
473 html_str = '%sTIMEOUT</td>\n' % html_str
474 else:
475 html_str = '%s<td bgcolor=\"magenta\">Not implemented</td>\n' % html_str
476
477 return html_str
478
479
480def render_html_report(test_cases, client_langs, server_langs, resultset,
481 num_failures):
482 """Generate html report."""
483 sorted_test_cases = sorted(test_cases)
484 sorted_client_langs = sorted(client_langs)
485 print sorted_client_langs
486 sorted_server_langs = sorted(server_langs)
487 html_str = ('<!DOCTYPE html>\n'
488 '<html lang=\"en\">\n'
489 '<head><title>Interop Test Result</title></head>\n'
490 '<body>\n')
491 if num_failures > 1:
492 html_str = (
493 '%s<p><h2><font color=\"red\">%d tests failed!</font></h2></p>\n' %
494 (html_str, num_failures))
495 elif num_failures:
496 html_str = (
497 '%s<p><h2><font color=\"red\">%d test failed!</font></h2></p>\n' %
498 (html_str, num_failures))
499 else:
500 html_str = (
501 '%s<p><h2><font color=\"green\">All tests passed!</font></h2></p>\n' %
502 html_str)
503 if args.cloud_to_prod_auth or args.cloud_to_prod:
504 # Each column header is the client language.
505 html_str = ('%s<h2>Cloud to Prod</h2>\n'
506 '<table style=\"width:100%%\" border=\"1\">\n'
507 '<tr bgcolor=\"#00BFFF\">\n'
508 '<th/>\n') % html_str
509 for client_lang in sorted_client_langs:
510 html_str = '%s<th>%s\n' % (html_str, client_lang)
511 html_str = '%s</tr>\n' % html_str
512 for test_case in sorted_test_cases:
513 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, test_case)
514 for client_lang in sorted_client_langs:
515 if args.cloud_to_prod:
516 shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case)
517 else:
518 shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case)
519 html_str = fill_one_test_result(shortname, resultset, html_str)
520 html_str = '%s</tr>\n' % html_str
521 html_str = '%s</table>\n' % html_str
522 if servers:
523 for test_case in sorted_test_cases:
524 # Each column header is the client language.
525 html_str = ('%s<h2>%s</h2>\n'
526 '<table style=\"width:100%%\" border=\"1\">\n'
527 '<tr bgcolor=\"#00BFFF\">\n'
528 '<th/>\n') % (html_str, test_case)
529 for client_lang in sorted_client_langs:
530 html_str = '%s<th>%s\n' % (html_str, client_lang)
531 html_str = '%s</tr>\n' % html_str
532 # Each row head is the server language.
533 for server_lang in sorted_server_langs:
534 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, server_lang)
535 # Fill up the cells with test result.
536 for client_lang in sorted_client_langs:
537 shortname = 'cloud_to_cloud:%s:%s_server:%s' % (
538 client_lang, server_lang, test_case)
539 html_str = fill_one_test_result(shortname, resultset, html_str)
540 html_str = '%s</tr>\n' % html_str
541 html_str = '%s</table>\n' % html_str
542
543 html_str = ('%s\n'
544 '<script>\n'
545 '$(document).ready(function(){'
546 '$(\'[data-toggle=\"tooltip\"]\').tooltip();\n'
547 '});\n'
548 '</script>\n'
549 '</body>\n'
550 '</html>') % html_str
551
552 # Write to reports/index.html as set up in Jenkins plugin.
553 html_report_dir = 'reports'
554 if not os.path.exists(html_report_dir):
555 os.mkdir(html_report_dir)
556 html_file_path = os.path.join(html_report_dir, 'index.html')
557 with open(html_file_path, 'w') as f:
558 f.write(html_str)
559
560
Jan Tattermusch320bd612015-09-15 12:44:35 -0700561argp = argparse.ArgumentParser(description='Run interop tests.')
562argp.add_argument('-l', '--language',
563 choices=['all'] + sorted(_LANGUAGES),
564 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700565 default=['all'],
566 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700567argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700568argp.add_argument('--cloud_to_prod',
569 default=False,
570 action='store_const',
571 const=True,
572 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700573argp.add_argument('--cloud_to_prod_auth',
574 default=False,
575 action='store_const',
576 const=True,
577 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700578argp.add_argument('-s', '--server',
579 choices=['all'] + sorted(_SERVERS),
580 action='append',
581 help='Run cloud_to_cloud servers in a separate docker ' +
582 'image. Servers can only be started automatically if ' +
583 '--use_docker option is enabled.',
584 default=[])
585argp.add_argument('--override_server',
586 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700587 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700588 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
589 default=[])
590argp.add_argument('-t', '--travis',
591 default=False,
592 action='store_const',
593 const=True)
594argp.add_argument('--use_docker',
595 default=False,
596 action='store_const',
597 const=True,
598 help='Run all the interop tests under docker. That provides ' +
599 'additional isolation and prevents the need to install ' +
600 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700601argp.add_argument('--allow_flakes',
602 default=False,
603 action='store_const',
604 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700605 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Jan Tattermusch320bd612015-09-15 12:44:35 -0700606args = argp.parse_args()
607
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700608servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700609 if x == 'all' else [x]
610 for x in args.server))
611
612if args.use_docker:
613 if not args.travis:
614 print 'Seen --use_docker flag, will run interop tests under docker.'
615 print
616 print 'IMPORTANT: The changes you are testing need to be locally committed'
617 print 'because only the committed changes in the current branch will be'
618 print 'copied to the docker environment.'
619 time.sleep(5)
620
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700621if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700622 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700623 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700624
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700625languages = set(_LANGUAGES[l]
626 for l in itertools.chain.from_iterable(
627 _LANGUAGES.iterkeys() if x == 'all' else [x]
628 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700629
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700630docker_images={}
631if args.use_docker:
632 # languages for which to build docker images
633 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
634 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700635
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700636 build_jobs = []
637 for l in languages_to_build:
638 job = build_interop_image_jobspec(l)
639 docker_images[str(l)] = job.tag
640 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700641
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700642 if build_jobs:
643 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700644 num_failures, _ = jobset.run(
645 build_jobs, newline_on_success=True, maxjobs=args.jobs)
646 if num_failures == 0:
647 jobset.message('SUCCESS', 'All docker images built successfully.',
648 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700649 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700650 jobset.message('FAILED', 'Failed to build interop docker images.',
651 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700652 for image in docker_images.itervalues():
653 dockerjob.remove_image(image, skip_nonexistent=True)
654 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700655
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700656# Start interop servers.
657server_jobs={}
658server_addresses={}
659try:
660 for s in servers:
661 lang = str(s)
662 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
663 job = dockerjob.DockerJob(spec)
664 server_jobs[lang] = job
665 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700666
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700667
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700668 jobs = []
669 if args.cloud_to_prod:
670 for language in languages:
671 for test_case in _TEST_CASES:
672 test_job = cloud_to_prod_jobspec(language, test_case,
673 docker_image=docker_images.get(str(language)))
674 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700675
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700676 if args.cloud_to_prod_auth:
677 for language in languages:
678 for test_case in _AUTH_TEST_CASES:
679 test_job = cloud_to_prod_jobspec(language, test_case,
680 docker_image=docker_images.get(str(language)),
681 auth=True)
682 jobs.append(test_job)
683
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700684 for server in args.override_server:
685 server_name = server[0]
686 (server_host, server_port) = server[1].split(':')
687 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700688
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700689 for server_name, server_address in server_addresses.iteritems():
690 (server_host, server_port) = server_address
691 for language in languages:
692 for test_case in _TEST_CASES:
693 test_job = cloud_to_cloud_jobspec(language,
694 test_case,
695 server_name,
696 server_host,
697 server_port,
698 docker_image=docker_images.get(str(language)))
699 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700700
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700701 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700702 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700703 for image in docker_images.itervalues():
704 dockerjob.remove_image(image, skip_nonexistent=True)
705 sys.exit(1)
706
707 root = ET.Element('testsuites')
708 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
709
Adele Zhoue4c35612015-10-16 15:34:23 -0700710 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
711 maxjobs=args.jobs, xml_report=testsuite)
712 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700713 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700714 else:
715 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700716
717 tree = ET.ElementTree(root)
718 tree.write('report.xml', encoding='UTF-8')
Adele Zhoue4c35612015-10-16 15:34:23 -0700719
720 # Generate HTML report.
721 render_html_report(_TEST_CASES, set([str(l) for l in languages]), servers,
722 resultset, num_failures)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700723
724finally:
725 # Check if servers are still running.
726 for server, job in server_jobs.iteritems():
727 if not job.is_running():
728 print 'Server "%s" has exited prematurely.' % server
729
730 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
731
732 for image in docker_images.itervalues():
733 print 'Removing docker image %s' % image
734 dockerjob.remove_image(image)