blob: 9a925bee4e1d240dde9432bd5b8f8c3d25a02370 [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
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -080034import atexit
Jan Tattermusch91ad0182015-10-01 09:22:03 -070035import dockerjob
Jan Tattermusch320bd612015-09-15 12:44:35 -070036import itertools
Jan Tattermusch320bd612015-09-15 12:44:35 -070037import jobset
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -080038import json
Jan Tattermusch210a0ea2015-10-02 15:05:36 -070039import multiprocessing
Jan Tattermusch8266c672015-09-17 09:18:03 -070040import os
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -080041import re
Adele Zhoua30f8292015-11-02 13:15:46 -080042import report_utils
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -080043import subprocess
Jan Tattermusch8266c672015-09-17 09:18:03 -070044import sys
Jan Tattermusch91ad0182015-10-01 09:22:03 -070045import tempfile
Jan Tattermusch8266c672015-09-17 09:18:03 -070046import time
Jan Tattermusch91ad0182015-10-01 09:22:03 -070047import uuid
Jan Tattermusch320bd612015-09-15 12:44:35 -070048
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -080049# Docker doesn't clean up after itself, so we do it on exit.
50atexit.register(lambda: subprocess.call(['stty', 'echo']))
51
Jan Tattermusch91ad0182015-10-01 09:22:03 -070052ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
53os.chdir(ROOT)
54
55_DEFAULT_SERVER_PORT=8080
Jan Tattermuschf49936a2015-09-16 15:44:26 -070056
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070057# TOOD(jtattermusch) wrapped languages use this variable for location
Jan Tattermuschf49936a2015-09-16 15:44:26 -070058# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
59# supported by C core SslCredentials instead.
60_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
61
Jan Tattermusch8266c672015-09-17 09:18:03 -070062
Jan Tattermuschf49936a2015-09-16 15:44:26 -070063class CXXLanguage:
64
65 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -070066 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -070067 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -070068 self.safename = 'cxx'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070069
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070070 def client_cmd(self, args):
71 return ['bins/opt/interop_client'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -070072
Jan Tattermuschf49936a2015-09-16 15:44:26 -070073 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070074 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070075
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070076 def server_cmd(self, args):
77 return ['bins/opt/interop_server', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -070078
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070079 def global_env(self):
80 return {}
81
Jan Tattermusch289b7b92015-10-21 18:09:59 -070082 def unimplemented_test_cases(self):
83 return []
84
Jan Tattermuschf49936a2015-09-16 15:44:26 -070085 def __str__(self):
86 return 'c++'
87
88
89class CSharpLanguage:
90
91 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -070092 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070093 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -070094 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -070095
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070096 def client_cmd(self, args):
97 return ['mono', 'Grpc.IntegrationTesting.Client.exe'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -070098
Jan Tattermuschf49936a2015-09-16 15:44:26 -070099 def cloud_to_prod_env(self):
100 return _SSL_CERT_ENV
101
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700102 def server_cmd(self, args):
103 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700104
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700105 def global_env(self):
106 return {}
107
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700108 def unimplemented_test_cases(self):
109 return []
110
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700111 def __str__(self):
112 return 'csharp'
113
114
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700115class JavaLanguage:
116
117 def __init__(self):
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700118 self.client_cwd = '../grpc-java'
119 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700120 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700121
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700122 def client_cmd(self, args):
123 return ['./run-test-client.sh'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700124
125 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700126 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700127
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700128 def server_cmd(self, args):
129 return ['./run-test-server.sh', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700130
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700131 def global_env(self):
132 return {}
133
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700134 def unimplemented_test_cases(self):
135 return []
136
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700137 def __str__(self):
138 return 'java'
139
140
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700141class GoLanguage:
142
143 def __init__(self):
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700144 # TODO: this relies on running inside docker
145 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
146 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700147 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700148
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700149 def client_cmd(self, args):
150 return ['go', 'run', 'client.go'] + args
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700151
152 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700153 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700154
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700155 def server_cmd(self, args):
156 return ['go', 'run', 'server.go', '--use_tls=true'] + args
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700157
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700158 def global_env(self):
159 return {}
160
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700161 def unimplemented_test_cases(self):
162 return []
163
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700164 def __str__(self):
165 return 'go'
166
167
Carl Mastrangelode449102015-10-28 11:05:49 -0700168class Http2Client:
169 """Represents the HTTP/2 Interop Test
170
171 This pretends to be a language in order to be built and run, but really it
172 isn't.
173 """
174 def __init__(self):
175 self.client_cwd = None
176 self.safename = str(self)
177
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800178 def client_cmd(self, args):
179 return ['tools/http2_interop/http2_interop.test', '-test.v'] + args
Carl Mastrangelode449102015-10-28 11:05:49 -0700180
181 def cloud_to_prod_env(self):
182 return {}
183
184 def global_env(self):
185 return {}
186
187 def unimplemented_test_cases(self):
188 return _TEST_CASES
189
190 def __str__(self):
191 return 'http2'
192
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700193class NodeLanguage:
194
195 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700196 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700197 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700198 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700199
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700200 def client_cmd(self, args):
201 return ['node', 'src/node/interop/interop_client.js'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700202
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700203 def cloud_to_prod_env(self):
204 return _SSL_CERT_ENV
205
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700206 def server_cmd(self, args):
207 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700208
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700209 def global_env(self):
210 return {}
211
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700212 def unimplemented_test_cases(self):
213 return []
214
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700215 def __str__(self):
216 return 'node'
217
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700218
219class PHPLanguage:
220
221 def __init__(self):
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700222 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700223 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700224
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700225 def client_cmd(self, args):
226 return ['src/php/bin/interop_client.sh'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700227
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700228 def cloud_to_prod_env(self):
229 return _SSL_CERT_ENV
230
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700231 def global_env(self):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700232 return {}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700233
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700234 def unimplemented_test_cases(self):
235 return []
236
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700237 def __str__(self):
238 return 'php'
239
240
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700241class RubyLanguage:
242
243 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700244 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700245 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700246 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700247
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700248 def client_cmd(self, args):
249 return ['ruby', 'src/ruby/bin/interop/interop_client.rb'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700250
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700251 def cloud_to_prod_env(self):
252 return _SSL_CERT_ENV
253
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700254 def server_cmd(self, args):
255 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700256
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700257 def global_env(self):
258 return {}
259
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700260 def unimplemented_test_cases(self):
261 return []
262
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700263 def __str__(self):
264 return 'ruby'
265
266
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700267class PythonLanguage:
268
269 def __init__(self):
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700270 self.client_cwd = None
271 self.server_cwd = None
272 self.safename = str(self)
273
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700274 def client_cmd(self, args):
275 return [
Masood Malekghassemieaa7d202015-12-07 14:38:53 -0800276 'src/python/grpcio/.tox/py27/bin/python',
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700277 'src/python/grpcio/setup.py',
278 'run_interop',
279 '--client',
280 '--args=\'{}\''.format(' '.join(args))
281 ]
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700282
283 def cloud_to_prod_env(self):
284 return _SSL_CERT_ENV
285
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700286 def server_cmd(self, args):
287 return [
Masood Malekghassemieaa7d202015-12-07 14:38:53 -0800288 'src/python/grpcio/.tox/py27/bin/python',
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700289 'src/python/grpcio/setup.py',
290 'run_interop',
291 '--server',
292 '--args=\'{}\''.format(' '.join(args) + ' --use_tls=true')
293 ]
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700294
295 def global_env(self):
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700296 return {'LD_LIBRARY_PATH': '{}/libs/opt'.format(DOCKER_WORKDIR_ROOT)}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700297
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700298 def unimplemented_test_cases(self):
299 return ['jwt_token_creds', 'per_rpc_creds']
300
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700301 def __str__(self):
302 return 'python'
303
304
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700305_LANGUAGES = {
306 'c++' : CXXLanguage(),
307 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700308 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700309 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700310 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700311 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700312 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700313 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700314}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700315
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700316# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700317_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700318
Jan Tattermusch320bd612015-09-15 12:44:35 -0700319_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700320 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700321 'cancel_after_begin', 'cancel_after_first_response',
322 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700323
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700324_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
325 'oauth2_auth_token', 'per_rpc_creds']
326
Carl Mastrangelo2dd55db2015-11-19 10:51:48 -0800327_HTTP2_TEST_CASES = ["tls", "framing"]
Jan Tattermusch8266c672015-09-17 09:18:03 -0700328
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700329DOCKER_WORKDIR_ROOT = '/var/local/git/grpc'
330
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700331def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
332 """Wraps given cmdline array to create 'docker run' cmdline from it."""
333 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
334
335 # turn environ into -e docker args
336 if environ:
337 for k,v in environ.iteritems():
338 docker_cmdline += ['-e', '%s=%s' % (k,v)]
339
340 # set working directory
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700341 workdir = DOCKER_WORKDIR_ROOT
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700342 if cwd:
343 workdir = os.path.join(workdir, cwd)
344 docker_cmdline += ['-w', workdir]
345
346 docker_cmdline += docker_args + [image] + cmdline
347 return docker_cmdline
348
349
350def bash_login_cmdline(cmdline):
351 """Creates bash -l -c cmdline from args list."""
352 # Use login shell:
353 # * rvm and nvm require it
354 # * makes error messages clearer if executables are missing
355 return ['bash', '-l', '-c', ' '.join(cmdline)]
356
357
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800358def auth_options(language, test_case):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700359 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
360
361 language = str(language)
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800362 cmdargs = []
363 env = {}
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700364
365 # TODO(jtattermusch): this file path only works inside docker
366 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
367 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
368 key_file_arg = '--service_account_key_file=%s' % key_filepath
369 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
370
371 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700372 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700373 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
374 else:
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800375 cmdargs += [key_file_arg]
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700376
377 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800378 cmdargs += [oauth_scope_arg]
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700379
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700380 if test_case == 'oauth2_auth_token' and language == 'c++':
381 # C++ oauth2 test uses GCE creds and thus needs to know the default account
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800382 cmdargs += [default_account_arg]
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700383
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700384 if test_case == 'compute_engine_creds':
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800385 cmdargs += [oauth_scope_arg, default_account_arg]
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700386
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800387 return (cmdargs, env)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700388
389
Jan Tattermusche2686282015-10-08 16:27:07 -0700390def _job_kill_handler(job):
391 if job._spec.container_name:
392 dockerjob.docker_kill(job._spec.container_name)
Jan Tattermusch39e3cb32015-10-22 18:21:08 -0700393 # When the job times out and we decide to kill it,
394 # we need to wait a before restarting the job
395 # to prevent "container name already in use" error.
396 # TODO(jtattermusch): figure out a cleaner way to to this.
397 time.sleep(2)
Jan Tattermusche2686282015-10-08 16:27:07 -0700398
399
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700400def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700401 """Creates jobspec for cloud-to-prod interop test"""
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800402 container_name = None
403 cmdargs = [
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700404 '--server_host_override=grpc-test.sandbox.google.com',
405 '--server_host=grpc-test.sandbox.google.com',
406 '--server_port=443',
407 '--use_tls=true',
408 '--test_case=%s' % test_case]
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700409 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700410 if auth:
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800411 auth_cmdargs, auth_env = auth_options(language, test_case)
412 cmdargs += auth_cmdargs
413 environ.update(auth_env)
414 cmdline = bash_login_cmdline(language.client_cmd(cmdargs))
415 cwd = language.client_cwd
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700416
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700417 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700418 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700419 cmdline = docker_run_cmdline(cmdline,
420 image=docker_image,
421 cwd=cwd,
422 environ=environ,
423 docker_args=['--net=host',
424 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700425 cwd = None
426 environ = None
427
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700428 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700429 test_job = jobset.JobSpec(
430 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700431 cwd=cwd,
432 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700433 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700434 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700435 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700436 timeout_retries=2 if args.allow_flakes else 0,
437 kill_handler=_job_kill_handler)
438 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700439 return test_job
440
Jan Tattermusch8266c672015-09-17 09:18:03 -0700441
442def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700443 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700444 """Creates jobspec for cloud-to-cloud interop test"""
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700445 cmdline = bash_login_cmdline(language.client_cmd([
446 '--server_host_override=foo.test.google.fr',
447 '--use_tls=true',
448 '--use_test_ca=true',
449 '--test_case=%s' % test_case,
450 '--server_host=%s' % server_host,
451 '--server_port=%s' % server_port]))
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700452 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700453 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700454 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700455 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700456 cmdline = docker_run_cmdline(cmdline,
457 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700458 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700459 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700460 docker_args=['--net=host',
461 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700462 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700463
Jan Tattermusch8266c672015-09-17 09:18:03 -0700464 test_job = jobset.JobSpec(
465 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700466 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700467 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700468 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700469 test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700470 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700471 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700472 timeout_retries=2 if args.allow_flakes else 0,
473 kill_handler=_job_kill_handler)
474 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700475 return test_job
476
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700477
478def server_jobspec(language, docker_image):
479 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700480 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700481 cmdline = bash_login_cmdline(
482 language.server_cmd(['--port=%s' % _DEFAULT_SERVER_PORT]))
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700483 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700484 docker_cmdline = docker_run_cmdline(cmdline,
485 image=docker_image,
486 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700487 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700488 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700489 '--name', container_name])
Carl Mastrangelode449102015-10-28 11:05:49 -0700490
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700491 server_job = jobset.JobSpec(
492 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700493 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700494 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700495 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700496 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700497 return server_job
498
499
500def build_interop_image_jobspec(language, tag=None):
501 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700502 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700503 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700504 env = {'INTEROP_IMAGE': tag,
505 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700506 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700507 env['TTY_FLAG'] = '-t'
508 # This env variable is used to get around the github rate limit
509 # error when running the PHP `composer install` command
510 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700511 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700512 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700513 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700514 build_job = jobset.JobSpec(
515 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700516 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700517 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700518 timeout_seconds=30*60)
519 build_job.tag = tag
520 return build_job
521
522
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -0800523def aggregate_http2_results(stdout):
524 match = re.search(r'\{"cases[^\]]*\]\}', stdout)
525 if not match:
526 return None
527
528 results = json.loads(match.group(0))
529 skipped = 0
530 passed = 0
531 failed = 0
532 failed_cases = []
533 for case in results['cases']:
534 if case.get('skipped', False):
535 skipped += 1
536 else:
537 if case.get('passed', False):
538 passed += 1
539 else:
540 failed += 1
541 failed_cases.append(case.get('name', "NONAME"))
542 return {
543 'passed': passed,
544 'failed': failed,
545 'skipped': skipped,
546 'failed_cases': ', '.join(failed_cases),
547 'percent': 1.0 * passed / (passed + failed)
548 }
549
Jan Tattermusch320bd612015-09-15 12:44:35 -0700550argp = argparse.ArgumentParser(description='Run interop tests.')
551argp.add_argument('-l', '--language',
552 choices=['all'] + sorted(_LANGUAGES),
553 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700554 default=['all'],
555 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700556argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700557argp.add_argument('--cloud_to_prod',
558 default=False,
559 action='store_const',
560 const=True,
561 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700562argp.add_argument('--cloud_to_prod_auth',
563 default=False,
564 action='store_const',
565 const=True,
566 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700567argp.add_argument('-s', '--server',
568 choices=['all'] + sorted(_SERVERS),
569 action='append',
570 help='Run cloud_to_cloud servers in a separate docker ' +
571 'image. Servers can only be started automatically if ' +
572 '--use_docker option is enabled.',
573 default=[])
574argp.add_argument('--override_server',
575 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700576 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700577 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
578 default=[])
579argp.add_argument('-t', '--travis',
580 default=False,
581 action='store_const',
582 const=True)
583argp.add_argument('--use_docker',
584 default=False,
585 action='store_const',
586 const=True,
587 help='Run all the interop tests under docker. That provides ' +
588 'additional isolation and prevents the need to install ' +
589 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700590argp.add_argument('--allow_flakes',
591 default=False,
592 action='store_const',
593 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700594 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Carl Mastrangelode449102015-10-28 11:05:49 -0700595argp.add_argument('--http2_interop',
596 default=False,
597 action='store_const',
598 const=True,
599 help='Enable HTTP/2 interop tests')
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800600
Jan Tattermusch320bd612015-09-15 12:44:35 -0700601args = argp.parse_args()
602
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700603servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700604 if x == 'all' else [x]
605 for x in args.server))
606
607if args.use_docker:
608 if not args.travis:
609 print 'Seen --use_docker flag, will run interop tests under docker.'
610 print
611 print 'IMPORTANT: The changes you are testing need to be locally committed'
612 print 'because only the committed changes in the current branch will be'
613 print 'copied to the docker environment.'
614 time.sleep(5)
615
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700616if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700617 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700618 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700619
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700620languages = set(_LANGUAGES[l]
621 for l in itertools.chain.from_iterable(
622 _LANGUAGES.iterkeys() if x == 'all' else [x]
623 for x in args.language))
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800624
Carl Mastrangelode449102015-10-28 11:05:49 -0700625http2Interop = Http2Client() if args.http2_interop else None
Jan Tattermusch320bd612015-09-15 12:44:35 -0700626
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700627docker_images={}
628if args.use_docker:
629 # languages for which to build docker images
630 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
631 [s for s in servers]))
Carl Mastrangelode449102015-10-28 11:05:49 -0700632 if args.http2_interop:
633 languages_to_build.add(http2Interop)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700634
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700635 build_jobs = []
636 for l in languages_to_build:
637 job = build_interop_image_jobspec(l)
638 docker_images[str(l)] = job.tag
639 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700640
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700641 if build_jobs:
642 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700643 num_failures, _ = jobset.run(
644 build_jobs, newline_on_success=True, maxjobs=args.jobs)
645 if num_failures == 0:
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800646 jobset.message('SUCCESS', 'All docker images built successfully.',
Adele Zhoue4c35612015-10-16 15:34:23 -0700647 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700648 else:
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800649 jobset.message('FAILED', 'Failed to build interop docker images.',
Adele Zhoue4c35612015-10-16 15:34:23 -0700650 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700651 for image in docker_images.itervalues():
652 dockerjob.remove_image(image, skip_nonexistent=True)
Carl Mastrangelo7a171402015-10-26 14:01:03 -0700653 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700654
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700655# Start interop servers.
656server_jobs={}
657server_addresses={}
658try:
659 for s in servers:
660 lang = str(s)
661 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
662 job = dockerjob.DockerJob(spec)
663 server_jobs[lang] = job
664 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700665
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700666
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700667 jobs = []
668 if args.cloud_to_prod:
669 for language in languages:
670 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700671 if not test_case in language.unimplemented_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)
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800675
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -0800676 if args.http2_interop:
Carl Mastrangelode449102015-10-28 11:05:49 -0700677 for test_case in _HTTP2_TEST_CASES:
678 test_job = cloud_to_prod_jobspec(http2Interop, test_case,
679 docker_image=docker_images.get(str(http2Interop)))
680 jobs.append(test_job)
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800681
Jan Tattermusch320bd612015-09-15 12:44:35 -0700682
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700683 if args.cloud_to_prod_auth:
684 for language in languages:
685 for test_case in _AUTH_TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700686 if not test_case in language.unimplemented_test_cases():
687 test_job = cloud_to_prod_jobspec(language, test_case,
688 docker_image=docker_images.get(str(language)),
689 auth=True)
690 jobs.append(test_job)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700691
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700692 for server in args.override_server:
693 server_name = server[0]
694 (server_host, server_port) = server[1].split(':')
695 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700696
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700697 for server_name, server_address in server_addresses.iteritems():
698 (server_host, server_port) = server_address
699 for language in languages:
700 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700701 if not test_case in language.unimplemented_test_cases():
702 test_job = cloud_to_cloud_jobspec(language,
703 test_case,
704 server_name,
705 server_host,
706 server_port,
707 docker_image=docker_images.get(str(language)))
708 jobs.append(test_job)
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800709
Carl Mastrangelode449102015-10-28 11:05:49 -0700710 if args.http2_interop:
711 for test_case in _HTTP2_TEST_CASES:
Carl Mastrangelo3b2e1bd2015-11-06 14:31:55 -0800712 if server_name == "go":
713 # TODO(carl-mastrangelo): Reenable after https://github.com/grpc/grpc-go/issues/434
Jorge Canizales340661d2015-12-07 13:34:57 -0800714 continue
Carl Mastrangelode449102015-10-28 11:05:49 -0700715 test_job = cloud_to_cloud_jobspec(http2Interop,
716 test_case,
717 server_name,
718 server_host,
719 server_port,
720 docker_image=docker_images.get(str(http2Interop)))
721 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700722
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700723 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700724 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700725 for image in docker_images.itervalues():
726 dockerjob.remove_image(image, skip_nonexistent=True)
727 sys.exit(1)
728
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800729 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
Adele Zhou2271ab52015-10-28 13:59:14 -0700730 maxjobs=args.jobs)
Adele Zhoue4c35612015-10-16 15:34:23 -0700731 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700732 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700733 else:
734 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700735
Adele Zhou3bc7ba42015-11-05 10:21:58 -0800736 report_utils.render_junit_xml_report(resultset, 'report.xml')
Masood Malekghassemi0a9cc3c2015-11-09 12:24:05 -0800737
Carl Mastrangeloe7f8e8e2015-12-08 17:22:44 -0800738 for name, job in resultset.iteritems():
739 if "http2" in name:
740 job[0].http2results = aggregate_http2_results(job[0].message)
741
Adele Zhou3bc7ba42015-11-05 10:21:58 -0800742 report_utils.render_interop_html_report(
Adele Zhou2271ab52015-10-28 13:59:14 -0700743 set([str(l) for l in languages]), servers, _TEST_CASES, _AUTH_TEST_CASES,
Carl Mastrangelode449102015-10-28 11:05:49 -0700744 _HTTP2_TEST_CASES, resultset, num_failures,
745 args.cloud_to_prod_auth or args.cloud_to_prod, args.http2_interop)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700746
747finally:
748 # Check if servers are still running.
749 for server, job in server_jobs.iteritems():
750 if not job.is_running():
751 print 'Server "%s" has exited prematurely.' % server
752
753 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
754
755 for image in docker_images.itervalues():
756 print 'Removing docker image %s' % image
757 dockerjob.remove_image(image)