blob: e1d60b2de39230af2ba8bb989c36102bdb8a42a0 [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 Tattermusch289b7b92015-10-21 18:09:59 -070076 def unimplemented_test_cases(self):
77 return []
78
Jan Tattermuschf49936a2015-09-16 15:44:26 -070079 def __str__(self):
80 return 'c++'
81
82
83class CSharpLanguage:
84
85 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -070086 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070087 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -070088 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -070089
Jan Tattermuschc8b94412015-10-21 17:57:28 -070090 def client_args(self):
91 return ['mono', 'Grpc.IntegrationTesting.Client.exe']
Jan Tattermusch8266c672015-09-17 09:18:03 -070092
Jan Tattermuschf49936a2015-09-16 15:44:26 -070093 def cloud_to_prod_env(self):
94 return _SSL_CERT_ENV
95
Jan Tattermusch91ad0182015-10-01 09:22:03 -070096 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -070097 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070098
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070099 def global_env(self):
100 return {}
101
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700102 def unimplemented_test_cases(self):
103 return []
104
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700105 def __str__(self):
106 return 'csharp'
107
108
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700109class JavaLanguage:
110
111 def __init__(self):
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700112 self.client_cwd = '../grpc-java'
113 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700114 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700115
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700116 def client_args(self):
117 return ['./run-test-client.sh']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700118
119 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700120 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700121
122 def server_args(self):
123 return ['./run-test-server.sh', '--use_tls=true']
124
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700125 def global_env(self):
126 return {}
127
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700128 def unimplemented_test_cases(self):
129 return []
130
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700131 def __str__(self):
132 return 'java'
133
134
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700135class GoLanguage:
136
137 def __init__(self):
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700138 # TODO: this relies on running inside docker
139 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
140 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700141 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700142
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700143 def client_args(self):
144 return ['go', 'run', 'client.go']
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700145
146 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700147 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700148
149 def server_args(self):
150 return ['go', 'run', 'server.go', '--use_tls=true']
151
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700152 def global_env(self):
153 return {}
154
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700155 def unimplemented_test_cases(self):
156 return []
157
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700158 def __str__(self):
159 return 'go'
160
161
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700162class NodeLanguage:
163
164 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700165 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700166 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700167 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700168
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700169 def client_args(self):
170 return ['node', 'src/node/interop/interop_client.js']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700171
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700172 def cloud_to_prod_env(self):
173 return _SSL_CERT_ENV
174
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700175 def server_args(self):
176 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
177
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700178 def global_env(self):
179 return {}
180
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700181 def unimplemented_test_cases(self):
182 return []
183
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700184 def __str__(self):
185 return 'node'
186
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700187
188class PHPLanguage:
189
190 def __init__(self):
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700191 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700192 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700193
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700194 def client_args(self):
195 return ['src/php/bin/interop_client.sh']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700196
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700197 def cloud_to_prod_env(self):
198 return _SSL_CERT_ENV
199
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700200 def global_env(self):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700201 return {}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700202
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700203 def unimplemented_test_cases(self):
204 return []
205
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700206 def __str__(self):
207 return 'php'
208
209
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700210class RubyLanguage:
211
212 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700213 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700214 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700215 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700216
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700217 def client_args(self):
218 return ['ruby', 'src/ruby/bin/interop/interop_client.rb']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700219
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700220 def cloud_to_prod_env(self):
221 return _SSL_CERT_ENV
222
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700223 def server_args(self):
Jan Tattermusch73b3eea2015-10-15 17:52:06 -0700224 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700225
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700226 def global_env(self):
227 return {}
228
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700229 def unimplemented_test_cases(self):
230 return []
231
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700232 def __str__(self):
233 return 'ruby'
234
235
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700236class PythonLanguage:
237
238 def __init__(self):
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700239 self.client_cwd = None
240 self.server_cwd = None
241 self.safename = str(self)
242
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700243 def client_args(self):
244 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700245
246 def cloud_to_prod_env(self):
247 return _SSL_CERT_ENV
248
249 def server_args(self):
Jan Tattermusch785efd42015-10-15 17:20:22 -0700250 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls=true']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700251
252 def global_env(self):
253 return {'LD_LIBRARY_PATH': 'libs/opt'}
254
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700255 def unimplemented_test_cases(self):
256 return ['jwt_token_creds', 'per_rpc_creds']
257
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700258 def __str__(self):
259 return 'python'
260
261
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700262_LANGUAGES = {
263 'c++' : CXXLanguage(),
264 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700265 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700266 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700267 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700268 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700269 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700270 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700271}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700272
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700273# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700274_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700275
Jan Tattermusch320bd612015-09-15 12:44:35 -0700276_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700277 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700278 'cancel_after_begin', 'cancel_after_first_response',
279 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700280
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700281_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
282 'oauth2_auth_token', 'per_rpc_creds']
283
Jan Tattermusch8266c672015-09-17 09:18:03 -0700284
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700285def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
286 """Wraps given cmdline array to create 'docker run' cmdline from it."""
287 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
288
289 # turn environ into -e docker args
290 if environ:
291 for k,v in environ.iteritems():
292 docker_cmdline += ['-e', '%s=%s' % (k,v)]
293
294 # set working directory
295 workdir = '/var/local/git/grpc'
296 if cwd:
297 workdir = os.path.join(workdir, cwd)
298 docker_cmdline += ['-w', workdir]
299
300 docker_cmdline += docker_args + [image] + cmdline
301 return docker_cmdline
302
303
304def bash_login_cmdline(cmdline):
305 """Creates bash -l -c cmdline from args list."""
306 # Use login shell:
307 # * rvm and nvm require it
308 # * makes error messages clearer if executables are missing
309 return ['bash', '-l', '-c', ' '.join(cmdline)]
310
311
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700312def add_auth_options(language, test_case, cmdline, env):
313 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
314
315 language = str(language)
316 cmdline = list(cmdline)
317 env = env.copy()
318
319 # TODO(jtattermusch): this file path only works inside docker
320 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
321 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
322 key_file_arg = '--service_account_key_file=%s' % key_filepath
323 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
324
325 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700326 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700327 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
328 else:
329 cmdline += [key_file_arg]
330
331 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
332 cmdline += [oauth_scope_arg]
333
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700334 if test_case == 'oauth2_auth_token' and language == 'c++':
335 # C++ oauth2 test uses GCE creds and thus needs to know the default account
336 cmdline += [default_account_arg]
337
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700338 if test_case == 'compute_engine_creds':
339 cmdline += [oauth_scope_arg, default_account_arg]
340
341 return (cmdline, env)
342
343
Jan Tattermusche2686282015-10-08 16:27:07 -0700344def _job_kill_handler(job):
345 if job._spec.container_name:
346 dockerjob.docker_kill(job._spec.container_name)
Jan Tattermusch39e3cb32015-10-22 18:21:08 -0700347 # When the job times out and we decide to kill it,
348 # we need to wait a before restarting the job
349 # to prevent "container name already in use" error.
350 # TODO(jtattermusch): figure out a cleaner way to to this.
351 time.sleep(2)
Jan Tattermusche2686282015-10-08 16:27:07 -0700352
353
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700354def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700355 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700356 cmdline = language.client_args() + [
357 '--server_host_override=grpc-test.sandbox.google.com',
358 '--server_host=grpc-test.sandbox.google.com',
359 '--server_port=443',
360 '--use_tls=true',
361 '--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700362 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700363 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700364 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700365 if auth:
366 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
367 cmdline = bash_login_cmdline(cmdline)
368
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700369 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700370 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700371 cmdline = docker_run_cmdline(cmdline,
372 image=docker_image,
373 cwd=cwd,
374 environ=environ,
375 docker_args=['--net=host',
376 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700377 cwd = None
378 environ = None
379
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700380 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700381 test_job = jobset.JobSpec(
382 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700383 cwd=cwd,
384 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700385 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700386 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700387 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700388 timeout_retries=2 if args.allow_flakes else 0,
389 kill_handler=_job_kill_handler)
390 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700391 return test_job
392
Jan Tattermusch8266c672015-09-17 09:18:03 -0700393
394def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700395 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700396 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700397 cmdline = bash_login_cmdline(language.client_args() +
398 ['--server_host_override=foo.test.google.fr',
399 '--use_tls=true',
400 '--use_test_ca=true',
401 '--test_case=%s' % test_case,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700402 '--server_host=%s' % server_host,
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700403 '--server_port=%s' % server_port])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700404 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700405 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700406 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700407 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700408 cmdline = docker_run_cmdline(cmdline,
409 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700410 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700411 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700412 docker_args=['--net=host',
413 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700414 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700415
Jan Tattermusch8266c672015-09-17 09:18:03 -0700416 test_job = jobset.JobSpec(
417 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700418 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700419 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700420 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700421 test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700422 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700423 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700424 timeout_retries=2 if args.allow_flakes else 0,
425 kill_handler=_job_kill_handler)
426 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700427 return test_job
428
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700429
430def server_jobspec(language, docker_image):
431 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700432 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700433 cmdline = bash_login_cmdline(language.server_args() +
434 ['--port=%s' % _DEFAULT_SERVER_PORT])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700435 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700436 docker_cmdline = docker_run_cmdline(cmdline,
437 image=docker_image,
438 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700439 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700440 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700441 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700442 server_job = jobset.JobSpec(
443 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700444 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700445 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700446 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700447 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700448 return server_job
449
450
451def build_interop_image_jobspec(language, tag=None):
452 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700453 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700454 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700455 env = {'INTEROP_IMAGE': tag,
456 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700457 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700458 env['TTY_FLAG'] = '-t'
459 # This env variable is used to get around the github rate limit
460 # error when running the PHP `composer install` command
461 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700462 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700463 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700464 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700465 build_job = jobset.JobSpec(
466 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700467 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700468 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700469 timeout_seconds=30*60)
470 build_job.tag = tag
471 return build_job
472
473
Adele Zhoue4c35612015-10-16 15:34:23 -0700474# TODO(adelez): Use mako template.
475def fill_one_test_result(shortname, resultset, html_str):
476 if shortname in resultset:
477 result = resultset[shortname]
478 if result.state == 'PASSED':
479 html_str = '%s<td bgcolor=\"green\">PASS</td>\n' % html_str
480 else:
481 tooltip = ''
482 if result.returncode > 0 or result.message:
483 if result.returncode > 0:
484 tooltip = 'returncode: %d ' % result.returncode
485 if result.message:
486 tooltip = '%smessage: %s' % (tooltip, result.message)
487 if result.state == 'FAILED':
488 html_str = '%s<td bgcolor=\"red\">' % html_str
489 if tooltip:
490 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
491 'data-placement=\"auto\" title=\"%s\">FAIL</a></td>\n' %
492 (html_str, tooltip))
493 else:
494 html_str = '%sFAIL</td>\n' % html_str
495 elif result.state == 'TIMEOUT':
496 html_str = '%s<td bgcolor=\"yellow\">' % html_str
497 if tooltip:
498 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
499 'data-placement=\"auto\" title=\"%s\">TIMEOUT</a></td>\n'
500 % (html_str, tooltip))
501 else:
502 html_str = '%sTIMEOUT</td>\n' % html_str
503 else:
504 html_str = '%s<td bgcolor=\"magenta\">Not implemented</td>\n' % html_str
505
506 return html_str
507
508
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700509def render_html_report(client_langs, server_langs, resultset,
Adele Zhoue4c35612015-10-16 15:34:23 -0700510 num_failures):
511 """Generate html report."""
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700512 sorted_test_cases = sorted(_TEST_CASES)
513 sorted_auth_test_cases = sorted(_AUTH_TEST_CASES)
Adele Zhoue4c35612015-10-16 15:34:23 -0700514 sorted_client_langs = sorted(client_langs)
Adele Zhoue4c35612015-10-16 15:34:23 -0700515 sorted_server_langs = sorted(server_langs)
516 html_str = ('<!DOCTYPE html>\n'
517 '<html lang=\"en\">\n'
518 '<head><title>Interop Test Result</title></head>\n'
519 '<body>\n')
520 if num_failures > 1:
521 html_str = (
522 '%s<p><h2><font color=\"red\">%d tests failed!</font></h2></p>\n' %
523 (html_str, num_failures))
524 elif num_failures:
525 html_str = (
526 '%s<p><h2><font color=\"red\">%d test failed!</font></h2></p>\n' %
527 (html_str, num_failures))
528 else:
529 html_str = (
530 '%s<p><h2><font color=\"green\">All tests passed!</font></h2></p>\n' %
531 html_str)
532 if args.cloud_to_prod_auth or args.cloud_to_prod:
533 # Each column header is the client language.
534 html_str = ('%s<h2>Cloud to Prod</h2>\n'
535 '<table style=\"width:100%%\" border=\"1\">\n'
536 '<tr bgcolor=\"#00BFFF\">\n'
Adele Zhou51b6eae2015-10-22 11:00:50 -0700537 '<th>Client languages &#9658;</th>\n') % html_str
Adele Zhoue4c35612015-10-16 15:34:23 -0700538 for client_lang in sorted_client_langs:
539 html_str = '%s<th>%s\n' % (html_str, client_lang)
540 html_str = '%s</tr>\n' % html_str
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700541 for test_case in sorted_test_cases + sorted_auth_test_cases:
Adele Zhoue4c35612015-10-16 15:34:23 -0700542 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, test_case)
543 for client_lang in sorted_client_langs:
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700544 if not test_case in sorted_auth_test_cases:
Adele Zhoue4c35612015-10-16 15:34:23 -0700545 shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case)
546 else:
547 shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case)
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700548 html_str = fill_one_test_result(shortname, resultset, html_str)
Adele Zhoue4c35612015-10-16 15:34:23 -0700549 html_str = '%s</tr>\n' % html_str
550 html_str = '%s</table>\n' % html_str
551 if servers:
552 for test_case in sorted_test_cases:
553 # Each column header is the client language.
554 html_str = ('%s<h2>%s</h2>\n'
555 '<table style=\"width:100%%\" border=\"1\">\n'
556 '<tr bgcolor=\"#00BFFF\">\n'
Adele Zhou51b6eae2015-10-22 11:00:50 -0700557 '<th>Client languages &#9658;<br/>'
558 'Server languages &#9660;</th>\n') % (html_str, test_case)
Adele Zhoue4c35612015-10-16 15:34:23 -0700559 for client_lang in sorted_client_langs:
560 html_str = '%s<th>%s\n' % (html_str, client_lang)
561 html_str = '%s</tr>\n' % html_str
562 # Each row head is the server language.
563 for server_lang in sorted_server_langs:
564 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, server_lang)
565 # Fill up the cells with test result.
566 for client_lang in sorted_client_langs:
567 shortname = 'cloud_to_cloud:%s:%s_server:%s' % (
568 client_lang, server_lang, test_case)
569 html_str = fill_one_test_result(shortname, resultset, html_str)
570 html_str = '%s</tr>\n' % html_str
571 html_str = '%s</table>\n' % html_str
572
573 html_str = ('%s\n'
574 '<script>\n'
575 '$(document).ready(function(){'
576 '$(\'[data-toggle=\"tooltip\"]\').tooltip();\n'
577 '});\n'
578 '</script>\n'
579 '</body>\n'
580 '</html>') % html_str
581
582 # Write to reports/index.html as set up in Jenkins plugin.
583 html_report_dir = 'reports'
584 if not os.path.exists(html_report_dir):
585 os.mkdir(html_report_dir)
586 html_file_path = os.path.join(html_report_dir, 'index.html')
587 with open(html_file_path, 'w') as f:
588 f.write(html_str)
589
590
Jan Tattermusch320bd612015-09-15 12:44:35 -0700591argp = argparse.ArgumentParser(description='Run interop tests.')
592argp.add_argument('-l', '--language',
593 choices=['all'] + sorted(_LANGUAGES),
594 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700595 default=['all'],
596 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700597argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700598argp.add_argument('--cloud_to_prod',
599 default=False,
600 action='store_const',
601 const=True,
602 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700603argp.add_argument('--cloud_to_prod_auth',
604 default=False,
605 action='store_const',
606 const=True,
607 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700608argp.add_argument('-s', '--server',
609 choices=['all'] + sorted(_SERVERS),
610 action='append',
611 help='Run cloud_to_cloud servers in a separate docker ' +
612 'image. Servers can only be started automatically if ' +
613 '--use_docker option is enabled.',
614 default=[])
615argp.add_argument('--override_server',
616 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700617 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700618 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
619 default=[])
620argp.add_argument('-t', '--travis',
621 default=False,
622 action='store_const',
623 const=True)
624argp.add_argument('--use_docker',
625 default=False,
626 action='store_const',
627 const=True,
628 help='Run all the interop tests under docker. That provides ' +
629 'additional isolation and prevents the need to install ' +
630 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700631argp.add_argument('--allow_flakes',
632 default=False,
633 action='store_const',
634 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700635 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Jan Tattermusch320bd612015-09-15 12:44:35 -0700636args = argp.parse_args()
637
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700638servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700639 if x == 'all' else [x]
640 for x in args.server))
641
642if args.use_docker:
643 if not args.travis:
644 print 'Seen --use_docker flag, will run interop tests under docker.'
645 print
646 print 'IMPORTANT: The changes you are testing need to be locally committed'
647 print 'because only the committed changes in the current branch will be'
648 print 'copied to the docker environment.'
649 time.sleep(5)
650
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700651if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700652 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700653 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700654
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700655languages = set(_LANGUAGES[l]
656 for l in itertools.chain.from_iterable(
657 _LANGUAGES.iterkeys() if x == 'all' else [x]
658 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700659
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700660docker_images={}
661if args.use_docker:
662 # languages for which to build docker images
663 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
664 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700665
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700666 build_jobs = []
667 for l in languages_to_build:
668 job = build_interop_image_jobspec(l)
669 docker_images[str(l)] = job.tag
670 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700671
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700672 if build_jobs:
673 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700674 num_failures, _ = jobset.run(
675 build_jobs, newline_on_success=True, maxjobs=args.jobs)
676 if num_failures == 0:
677 jobset.message('SUCCESS', 'All docker images built successfully.',
678 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700679 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700680 jobset.message('FAILED', 'Failed to build interop docker images.',
681 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700682 for image in docker_images.itervalues():
683 dockerjob.remove_image(image, skip_nonexistent=True)
Carl Mastrangelo7a171402015-10-26 14:01:03 -0700684 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700685
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700686# Start interop servers.
687server_jobs={}
688server_addresses={}
689try:
690 for s in servers:
691 lang = str(s)
692 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
693 job = dockerjob.DockerJob(spec)
694 server_jobs[lang] = job
695 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700696
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700697
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700698 jobs = []
699 if args.cloud_to_prod:
700 for language in languages:
701 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700702 if not test_case in language.unimplemented_test_cases():
703 test_job = cloud_to_prod_jobspec(language, test_case,
704 docker_image=docker_images.get(str(language)))
705 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700706
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700707 if args.cloud_to_prod_auth:
708 for language in languages:
709 for test_case in _AUTH_TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700710 if not test_case in language.unimplemented_test_cases():
711 test_job = cloud_to_prod_jobspec(language, test_case,
712 docker_image=docker_images.get(str(language)),
713 auth=True)
714 jobs.append(test_job)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700715
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700716 for server in args.override_server:
717 server_name = server[0]
718 (server_host, server_port) = server[1].split(':')
719 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700720
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700721 for server_name, server_address in server_addresses.iteritems():
722 (server_host, server_port) = server_address
723 for language in languages:
724 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700725 if not test_case in language.unimplemented_test_cases():
726 test_job = cloud_to_cloud_jobspec(language,
727 test_case,
728 server_name,
729 server_host,
730 server_port,
731 docker_image=docker_images.get(str(language)))
732 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700733
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700734 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700735 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700736 for image in docker_images.itervalues():
737 dockerjob.remove_image(image, skip_nonexistent=True)
738 sys.exit(1)
739
740 root = ET.Element('testsuites')
741 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
742
Adele Zhoue4c35612015-10-16 15:34:23 -0700743 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
744 maxjobs=args.jobs, xml_report=testsuite)
745 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700746 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700747 else:
748 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700749
750 tree = ET.ElementTree(root)
751 tree.write('report.xml', encoding='UTF-8')
Adele Zhoue4c35612015-10-16 15:34:23 -0700752
753 # Generate HTML report.
Jan Tattermuschbfbd0382015-10-21 18:43:47 -0700754 render_html_report(set([str(l) for l in languages]), servers,
Adele Zhoue4c35612015-10-16 15:34:23 -0700755 resultset, num_failures)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700756
757finally:
758 # Check if servers are still running.
759 for server, job in server_jobs.iteritems():
760 if not job.is_running():
761 print 'Server "%s" has exited prematurely.' % server
762
763 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
764
765 for image in docker_images.itervalues():
766 print 'Removing docker image %s' % image
767 dockerjob.remove_image(image)