blob: 9b9f22c2c009ead8c911b30184f3e83087b0fffa [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)
347
348
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700349def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700350 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700351 cmdline = language.client_args() + [
352 '--server_host_override=grpc-test.sandbox.google.com',
353 '--server_host=grpc-test.sandbox.google.com',
354 '--server_port=443',
355 '--use_tls=true',
356 '--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700357 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700358 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700359 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700360 if auth:
361 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
362 cmdline = bash_login_cmdline(cmdline)
363
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700364 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700365 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700366 cmdline = docker_run_cmdline(cmdline,
367 image=docker_image,
368 cwd=cwd,
369 environ=environ,
370 docker_args=['--net=host',
371 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700372 cwd = None
373 environ = None
374
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700375 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700376 test_job = jobset.JobSpec(
377 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700378 cwd=cwd,
379 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700380 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700381 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700382 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700383 timeout_retries=2 if args.allow_flakes else 0,
384 kill_handler=_job_kill_handler)
385 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700386 return test_job
387
Jan Tattermusch8266c672015-09-17 09:18:03 -0700388
389def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700390 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700391 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700392 cmdline = bash_login_cmdline(language.client_args() +
393 ['--server_host_override=foo.test.google.fr',
394 '--use_tls=true',
395 '--use_test_ca=true',
396 '--test_case=%s' % test_case,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700397 '--server_host=%s' % server_host,
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700398 '--server_port=%s' % server_port])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700399 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700400 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700401 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700402 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700403 cmdline = docker_run_cmdline(cmdline,
404 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700405 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700406 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700407 docker_args=['--net=host',
408 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700409 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700410
Jan Tattermusch8266c672015-09-17 09:18:03 -0700411 test_job = jobset.JobSpec(
412 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700413 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700414 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700415 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700416 test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700417 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700418 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700419 timeout_retries=2 if args.allow_flakes else 0,
420 kill_handler=_job_kill_handler)
421 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700422 return test_job
423
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700424
425def server_jobspec(language, docker_image):
426 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700427 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700428 cmdline = bash_login_cmdline(language.server_args() +
429 ['--port=%s' % _DEFAULT_SERVER_PORT])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700430 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700431 docker_cmdline = docker_run_cmdline(cmdline,
432 image=docker_image,
433 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700434 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700435 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700436 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700437 server_job = jobset.JobSpec(
438 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700439 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700440 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700441 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700442 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700443 return server_job
444
445
446def build_interop_image_jobspec(language, tag=None):
447 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700448 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700449 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700450 env = {'INTEROP_IMAGE': tag,
451 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700452 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700453 env['TTY_FLAG'] = '-t'
454 # This env variable is used to get around the github rate limit
455 # error when running the PHP `composer install` command
456 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700457 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700458 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700459 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700460 build_job = jobset.JobSpec(
461 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700462 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700463 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700464 timeout_seconds=30*60)
465 build_job.tag = tag
466 return build_job
467
468
Adele Zhoue4c35612015-10-16 15:34:23 -0700469# TODO(adelez): Use mako template.
470def fill_one_test_result(shortname, resultset, html_str):
471 if shortname in resultset:
472 result = resultset[shortname]
473 if result.state == 'PASSED':
474 html_str = '%s<td bgcolor=\"green\">PASS</td>\n' % html_str
475 else:
476 tooltip = ''
477 if result.returncode > 0 or result.message:
478 if result.returncode > 0:
479 tooltip = 'returncode: %d ' % result.returncode
480 if result.message:
481 tooltip = '%smessage: %s' % (tooltip, result.message)
482 if result.state == 'FAILED':
483 html_str = '%s<td bgcolor=\"red\">' % html_str
484 if tooltip:
485 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
486 'data-placement=\"auto\" title=\"%s\">FAIL</a></td>\n' %
487 (html_str, tooltip))
488 else:
489 html_str = '%sFAIL</td>\n' % html_str
490 elif result.state == 'TIMEOUT':
491 html_str = '%s<td bgcolor=\"yellow\">' % html_str
492 if tooltip:
493 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
494 'data-placement=\"auto\" title=\"%s\">TIMEOUT</a></td>\n'
495 % (html_str, tooltip))
496 else:
497 html_str = '%sTIMEOUT</td>\n' % html_str
498 else:
499 html_str = '%s<td bgcolor=\"magenta\">Not implemented</td>\n' % html_str
500
501 return html_str
502
503
504def render_html_report(test_cases, client_langs, server_langs, resultset,
505 num_failures):
506 """Generate html report."""
507 sorted_test_cases = sorted(test_cases)
508 sorted_client_langs = sorted(client_langs)
509 print sorted_client_langs
510 sorted_server_langs = sorted(server_langs)
511 html_str = ('<!DOCTYPE html>\n'
512 '<html lang=\"en\">\n'
513 '<head><title>Interop Test Result</title></head>\n'
514 '<body>\n')
515 if num_failures > 1:
516 html_str = (
517 '%s<p><h2><font color=\"red\">%d tests failed!</font></h2></p>\n' %
518 (html_str, num_failures))
519 elif num_failures:
520 html_str = (
521 '%s<p><h2><font color=\"red\">%d test failed!</font></h2></p>\n' %
522 (html_str, num_failures))
523 else:
524 html_str = (
525 '%s<p><h2><font color=\"green\">All tests passed!</font></h2></p>\n' %
526 html_str)
527 if args.cloud_to_prod_auth or args.cloud_to_prod:
528 # Each column header is the client language.
529 html_str = ('%s<h2>Cloud to Prod</h2>\n'
530 '<table style=\"width:100%%\" border=\"1\">\n'
531 '<tr bgcolor=\"#00BFFF\">\n'
532 '<th/>\n') % html_str
533 for client_lang in sorted_client_langs:
534 html_str = '%s<th>%s\n' % (html_str, client_lang)
535 html_str = '%s</tr>\n' % html_str
536 for test_case in sorted_test_cases:
537 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, test_case)
538 for client_lang in sorted_client_langs:
539 if args.cloud_to_prod:
540 shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case)
541 else:
542 shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case)
543 html_str = fill_one_test_result(shortname, resultset, html_str)
544 html_str = '%s</tr>\n' % html_str
545 html_str = '%s</table>\n' % html_str
546 if servers:
547 for test_case in sorted_test_cases:
548 # Each column header is the client language.
549 html_str = ('%s<h2>%s</h2>\n'
550 '<table style=\"width:100%%\" border=\"1\">\n'
551 '<tr bgcolor=\"#00BFFF\">\n'
552 '<th/>\n') % (html_str, test_case)
553 for client_lang in sorted_client_langs:
554 html_str = '%s<th>%s\n' % (html_str, client_lang)
555 html_str = '%s</tr>\n' % html_str
556 # Each row head is the server language.
557 for server_lang in sorted_server_langs:
558 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, server_lang)
559 # Fill up the cells with test result.
560 for client_lang in sorted_client_langs:
561 shortname = 'cloud_to_cloud:%s:%s_server:%s' % (
562 client_lang, server_lang, test_case)
563 html_str = fill_one_test_result(shortname, resultset, html_str)
564 html_str = '%s</tr>\n' % html_str
565 html_str = '%s</table>\n' % html_str
566
567 html_str = ('%s\n'
568 '<script>\n'
569 '$(document).ready(function(){'
570 '$(\'[data-toggle=\"tooltip\"]\').tooltip();\n'
571 '});\n'
572 '</script>\n'
573 '</body>\n'
574 '</html>') % html_str
575
576 # Write to reports/index.html as set up in Jenkins plugin.
577 html_report_dir = 'reports'
578 if not os.path.exists(html_report_dir):
579 os.mkdir(html_report_dir)
580 html_file_path = os.path.join(html_report_dir, 'index.html')
581 with open(html_file_path, 'w') as f:
582 f.write(html_str)
583
584
Jan Tattermusch320bd612015-09-15 12:44:35 -0700585argp = argparse.ArgumentParser(description='Run interop tests.')
586argp.add_argument('-l', '--language',
587 choices=['all'] + sorted(_LANGUAGES),
588 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700589 default=['all'],
590 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700591argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700592argp.add_argument('--cloud_to_prod',
593 default=False,
594 action='store_const',
595 const=True,
596 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700597argp.add_argument('--cloud_to_prod_auth',
598 default=False,
599 action='store_const',
600 const=True,
601 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700602argp.add_argument('-s', '--server',
603 choices=['all'] + sorted(_SERVERS),
604 action='append',
605 help='Run cloud_to_cloud servers in a separate docker ' +
606 'image. Servers can only be started automatically if ' +
607 '--use_docker option is enabled.',
608 default=[])
609argp.add_argument('--override_server',
610 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700611 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700612 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
613 default=[])
614argp.add_argument('-t', '--travis',
615 default=False,
616 action='store_const',
617 const=True)
618argp.add_argument('--use_docker',
619 default=False,
620 action='store_const',
621 const=True,
622 help='Run all the interop tests under docker. That provides ' +
623 'additional isolation and prevents the need to install ' +
624 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700625argp.add_argument('--allow_flakes',
626 default=False,
627 action='store_const',
628 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700629 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Jan Tattermusch320bd612015-09-15 12:44:35 -0700630args = argp.parse_args()
631
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700632servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700633 if x == 'all' else [x]
634 for x in args.server))
635
636if args.use_docker:
637 if not args.travis:
638 print 'Seen --use_docker flag, will run interop tests under docker.'
639 print
640 print 'IMPORTANT: The changes you are testing need to be locally committed'
641 print 'because only the committed changes in the current branch will be'
642 print 'copied to the docker environment.'
643 time.sleep(5)
644
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700645if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700646 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700647 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700648
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700649languages = set(_LANGUAGES[l]
650 for l in itertools.chain.from_iterable(
651 _LANGUAGES.iterkeys() if x == 'all' else [x]
652 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700653
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700654docker_images={}
655if args.use_docker:
656 # languages for which to build docker images
657 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
658 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700659
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700660 build_jobs = []
661 for l in languages_to_build:
662 job = build_interop_image_jobspec(l)
663 docker_images[str(l)] = job.tag
664 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700665
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700666 if build_jobs:
667 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700668 num_failures, _ = jobset.run(
669 build_jobs, newline_on_success=True, maxjobs=args.jobs)
670 if num_failures == 0:
671 jobset.message('SUCCESS', 'All docker images built successfully.',
672 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700673 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700674 jobset.message('FAILED', 'Failed to build interop docker images.',
675 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700676 for image in docker_images.itervalues():
677 dockerjob.remove_image(image, skip_nonexistent=True)
678 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700679
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700680# Start interop servers.
681server_jobs={}
682server_addresses={}
683try:
684 for s in servers:
685 lang = str(s)
686 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
687 job = dockerjob.DockerJob(spec)
688 server_jobs[lang] = job
689 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700690
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700691
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700692 jobs = []
693 if args.cloud_to_prod:
694 for language in languages:
695 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700696 if not test_case in language.unimplemented_test_cases():
697 test_job = cloud_to_prod_jobspec(language, test_case,
698 docker_image=docker_images.get(str(language)))
699 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700700
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700701 if args.cloud_to_prod_auth:
702 for language in languages:
703 for test_case in _AUTH_TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700704 if not test_case in language.unimplemented_test_cases():
705 test_job = cloud_to_prod_jobspec(language, test_case,
706 docker_image=docker_images.get(str(language)),
707 auth=True)
708 jobs.append(test_job)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700709
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700710 for server in args.override_server:
711 server_name = server[0]
712 (server_host, server_port) = server[1].split(':')
713 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700714
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700715 for server_name, server_address in server_addresses.iteritems():
716 (server_host, server_port) = server_address
717 for language in languages:
718 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700719 if not test_case in language.unimplemented_test_cases():
720 test_job = cloud_to_cloud_jobspec(language,
721 test_case,
722 server_name,
723 server_host,
724 server_port,
725 docker_image=docker_images.get(str(language)))
726 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700727
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700728 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700729 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700730 for image in docker_images.itervalues():
731 dockerjob.remove_image(image, skip_nonexistent=True)
732 sys.exit(1)
733
734 root = ET.Element('testsuites')
735 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
736
Adele Zhoue4c35612015-10-16 15:34:23 -0700737 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
738 maxjobs=args.jobs, xml_report=testsuite)
739 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700740 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700741 else:
742 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700743
744 tree = ET.ElementTree(root)
745 tree.write('report.xml', encoding='UTF-8')
Adele Zhoue4c35612015-10-16 15:34:23 -0700746
747 # Generate HTML report.
748 render_html_report(_TEST_CASES, set([str(l) for l in languages]), servers,
749 resultset, num_failures)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700750
751finally:
752 # Check if servers are still running.
753 for server, job in server_jobs.iteritems():
754 if not job.is_running():
755 print 'Server "%s" has exited prematurely.' % server
756
757 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
758
759 for image in docker_images.itervalues():
760 print 'Removing docker image %s' % image
761 dockerjob.remove_image(image)